diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 1bd5d584..0b624d07 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -5,20 +5,20 @@ shazbert | https://github.com/shazbert
dependabot[bot] | https://github.com/apps/dependabot
gloriousCode | https://github.com/gloriousCode
dependabot-preview[bot] | https://github.com/apps/dependabot-preview
-xtda | https://github.com/xtda
gbjk | https://github.com/gbjk
+xtda | https://github.com/xtda
lrascao | https://github.com/lrascao
+Beadko | https://github.com/Beadko
Rots | https://github.com/Rots
vazha | https://github.com/vazha
ydm | https://github.com/ydm
ermalguni | https://github.com/ermalguni
MadCozBadd | https://github.com/MadCozBadd
-Beadko | https://github.com/Beadko
vadimzhukck | https://github.com/vadimzhukck
-140am | https://github.com/140am
-marcofranssen | https://github.com/marcofranssen
geseq | https://github.com/geseq
samuael | https://github.com/samuael
+marcofranssen | https://github.com/marcofranssen
+140am | https://github.com/140am
TaltaM | https://github.com/TaltaM
dackroyd | https://github.com/dackroyd
cranktakular | https://github.com/cranktakular
@@ -26,19 +26,19 @@ khcchiu | https://github.com/khcchiu
yangrq1018 | https://github.com/yangrq1018
woshidama323 | https://github.com/woshidama323
crackcomm | https://github.com/crackcomm
-azhang | https://github.com/azhang
+herenow | https://github.com/herenow
+tk42 | https://github.com/tk42
+soxipy | https://github.com/soxipy
andreygrehov | https://github.com/andreygrehov
+azhang | https://github.com/azhang
bretep | https://github.com/bretep
Christian-Achilli | https://github.com/Christian-Achilli
cornelk | https://github.com/cornelk
gam-phon | https://github.com/gam-phon
-herenow | https://github.com/herenow
if1live | https://github.com/if1live
lozdog245 | https://github.com/lozdog245
MarkDzulko | https://github.com/MarkDzulko
mshogin | https://github.com/mshogin
-soxipy | https://github.com/soxipy
-tk42 | https://github.com/tk42
blombard | https://github.com/blombard
cavapoo2 | https://github.com/cavapoo2
CodeLingoTeam | https://github.com/CodeLingoTeam
diff --git a/LICENSE b/LICENSE
index 6a735d58..3abf50b2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2014-2023 The GoCryptoTrader Developers
+Copyright (c) 2014-2024 The GoCryptoTrader Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 6720a4fa..ee0231f4 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Bybit | Yes | Yes | NA |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | NA |
+| Deribit | Yes | Yes | No |
| Exmo | Yes | NA | NA |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
@@ -141,25 +142,25 @@ Binaries will be published once the codebase reaches a stable condition.
|User|Contribution Amount|
|--|--|
-| [thrasher-](https://github.com/thrasher-) | 684 |
-| [shazbert](https://github.com/shazbert) | 315 |
-| [dependabot[bot]](https://github.com/apps/dependabot) | 228 |
-| [gloriousCode](https://github.com/gloriousCode) | 224 |
+| [thrasher-](https://github.com/thrasher-) | 690 |
+| [shazbert](https://github.com/shazbert) | 326 |
+| [dependabot[bot]](https://github.com/apps/dependabot) | 276 |
+| [gloriousCode](https://github.com/gloriousCode) | 233 |
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 |
+| [gbjk](https://github.com/gbjk) | 72 |
| [xtda](https://github.com/xtda) | 47 |
-| [gbjk](https://github.com/gbjk) | 42 |
| [lrascao](https://github.com/lrascao) | 27 |
+| [Beadko](https://github.com/Beadko) | 17 |
| [Rots](https://github.com/Rots) | 15 |
| [vazha](https://github.com/vazha) | 15 |
| [ydm](https://github.com/ydm) | 15 |
| [ermalguni](https://github.com/ermalguni) | 14 |
| [MadCozBadd](https://github.com/MadCozBadd) | 13 |
-| [Beadko](https://github.com/Beadko) | 11 |
| [vadimzhukck](https://github.com/vadimzhukck) | 10 |
-| [140am](https://github.com/140am) | 8 |
-| [marcofranssen](https://github.com/marcofranssen) | 8 |
| [geseq](https://github.com/geseq) | 8 |
-| [samuael](https://github.com/samuael) | 7 |
+| [samuael](https://github.com/samuael) | 8 |
+| [marcofranssen](https://github.com/marcofranssen) | 8 |
+| [140am](https://github.com/140am) | 8 |
| [TaltaM](https://github.com/TaltaM) | 6 |
| [dackroyd](https://github.com/dackroyd) | 5 |
| [cranktakular](https://github.com/cranktakular) | 5 |
@@ -167,8 +168,12 @@ Binaries will be published once the codebase reaches a stable condition.
| [yangrq1018](https://github.com/yangrq1018) | 4 |
| [woshidama323](https://github.com/woshidama323) | 3 |
| [crackcomm](https://github.com/crackcomm) | 3 |
-| [azhang](https://github.com/azhang) | 2 |
+| [mshogin](https://github.com/mshogin) | 2 |
+| [herenow](https://github.com/herenow) | 2 |
+| [tk42](https://github.com/tk42) | 2 |
+| [soxipy](https://github.com/soxipy) | 2 |
| [andreygrehov](https://github.com/andreygrehov) | 2 |
+| [azhang](https://github.com/azhang) | 2 |
| [bretep](https://github.com/bretep) | 2 |
| [Christian-Achilli](https://github.com/Christian-Achilli) | 2 |
| [cornelk](https://github.com/cornelk) | 2 |
diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go
index adf3db21..9416108e 100644
--- a/cmd/documentation/documentation.go
+++ b/cmd/documentation/documentation.go
@@ -173,6 +173,26 @@ func main() {
// Github API missing contributors
contributors = append(contributors, []Contributor{
+ {
+ Login: "andreygrehov",
+ URL: "https://github.com/andreygrehov",
+ Contributions: 2,
+ },
+ {
+ Login: "azhang",
+ URL: "https://github.com/azhang",
+ Contributions: 2,
+ },
+ {
+ Login: "bretep",
+ URL: "https://github.com/bretep",
+ Contributions: 2,
+ },
+ {
+ Login: "Christian-Achilli",
+ URL: "https://github.com/Christian-Achilli",
+ Contributions: 2,
+ },
{
Login: "cornelk",
URL: "https://github.com/cornelk",
diff --git a/cmd/documentation/exchanges_templates/deribit.tmpl b/cmd/documentation/exchanges_templates/deribit.tmpl
new file mode 100644
index 00000000..9e04bfa9
--- /dev/null
+++ b/cmd/documentation/exchanges_templates/deribit.tmpl
@@ -0,0 +1,106 @@
+{{define "exchanges deribit" -}}
+{{template "header" .}}
+## Deribit 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 d exchange.IBotExchange
+
+for i := range bot.Exchanges {
+ if bot.Exchanges[i].GetName() == "Deribit" {
+ d = bot.Exchanges[i]
+ }
+}
+
+// Public calls - wrapper functions
+
+// Fetches current ticker information
+tick, err := d.FetchTicker()
+if err != nil {
+ // Handle error
+}
+
+// Fetches current orderbook information
+ob, err := d.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 := d.GetAccountInfo()
+if err != nil {
+ // Handle error
+}
+```
+
++ If enabled via individually importing package, rudimentary example below:
+
+```go
+// Public calls
+
+// Fetches current ticker information
+tick, err := d.GetTicker()
+if err != nil {
+ // Handle error
+}
+
+// Fetches current orderbook information
+ob, err := d.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 := d.GetUserInfo(...)
+if err != nil {
+ // Handle error
+}
+
+// Submits an order and the exchange and returns its tradeID
+tradeID, err := d.Trade(...)
+if err != nil {
+ // Handle error
+}
+```
+
+### How to do Websocket public/private calls
+
+```go
+ // Exchanges will be abstracted out in further updates and examples will be
+ // supplied then
+```
+
+### Please click GoDocs chevron above to view current GoDoc information for this package
+{{template "contributions"}}
+{{template "donations" .}}
+{{end}}
diff --git a/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl b/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl
index d2ca59e6..d233b585 100644
--- a/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl
+++ b/cmd/documentation/exchanges_templates/exchanges_trade_readme.tmpl
@@ -54,6 +54,7 @@ _b in this context is an `IBotExchange` implemented struct_
| Bybit | Yes | Yes | Yes |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
+| Deribit | Yes | Yes | Yes |
| Exmo | Yes | NA | No |
| GateIO | Yes | Yes | No |
| Gemini | Yes | Yes | Yes |
diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl
index 19f4b31c..e5cd3674 100644
--- a/cmd/documentation/root_templates/root_readme.tmpl
+++ b/cmd/documentation/root_templates/root_readme.tmpl
@@ -32,6 +32,7 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
| Bybit | Yes | Yes | NA |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | NA |
+| Deribit | Yes | Yes | No |
| Exmo | Yes | NA | NA |
| GateIO | Yes | Yes | NA |
| Gemini | Yes | Yes | No |
diff --git a/cmd/exchange_wrapper_issues/wrapperconfig.json b/cmd/exchange_wrapper_issues/wrapperconfig.json
index 8ca64f4f..a37d5172 100644
--- a/cmd/exchange_wrapper_issues/wrapperconfig.json
+++ b/cmd/exchange_wrapper_issues/wrapperconfig.json
@@ -101,6 +101,11 @@
"clientID": "ClientID",
"otpSecret": "-"
},
+ "deribit": {
+ "key": "Key",
+ "secret": "Secret",
+ "otpSecret": "-"
+ },
"exmo": {
"key": "Key",
"secret": "Secret",
diff --git a/config_example.json b/config_example.json
index 2b643b2f..4a24ae1a 100644
--- a/config_example.json
+++ b/config_example.json
@@ -1288,6 +1288,146 @@
}
]
},
+ {
+ "name": "Deribit",
+ "enabled": true,
+ "verbose": false,
+ "httpTimeout": 15000000000,
+ "websocketResponseCheckTimeout": 30000000,
+ "websocketResponseMaxLimit": 7000000000,
+ "websocketTrafficTimeout": 30000000000,
+ "websocketOrderbookBufferLimit": 5,
+ "baseCurrencies": "BTC,ETH,SOL,USDC",
+ "currencyPairs": {
+ "assetTypes": [
+ "spot",
+ "futures",
+ "options",
+ "option_combo",
+ "future_combo"
+ ],
+ "pairs": {
+ "spot": {
+ "assetEnabled": true,
+ "enabled": "BTC_USDC,ETH_USDC,ETH_BTC",
+ "available": "BTC_USDC,ETH_USDC,ETH_BTC",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "_"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "_"
+ }
+ },
+ "futures": {
+ "assetEnabled": true,
+ "enabled": "BTC-PERPETUAL,ETH-PERPETUAL,SOL-PERPETUAL,AVAX_USDC-PERPETUAL,ETH_USDC-PERPETUAL,ADA_USDC-PERPETUAL,DOT_USDC-PERPETUAL,BTC_USDC-PERPETUAL,XRP_USDC-PERPETUAL,SOL_USDC-PERPETUAL,LTC_USDC-PERPETUAL,MATIC_USDC-PERPETUAL,UNI_USDC-PERPETUAL,LINK_USDC-PERPETUAL,ALGO_USDC-PERPETUAL",
+ "available": "BTC-21OCT22,BTC-28OCT22,BTC-25NOV22,BTC-30DEC22,BTC-31MAR23,BTC-30JUN23,BTC-29SEP23,BTC-PERPETUAL,ETH-21OCT22,ETH-28OCT22,ETH-25NOV22,ETH-30DEC22,ETH-31MAR23,ETH-30JUN23,ETH-29SEP23,ETH-PERPETUAL,SOL-21OCT22,SOL-28OCT22,SOL-25NOV22,SOL-30DEC22,SOL-PERPETUAL,AVAX_USDC-PERPETUAL,ETH_USDC-PERPETUAL,ADA_USDC-PERPETUAL,DOT_USDC-PERPETUAL,BTC_USDC-PERPETUAL,XRP_USDC-PERPETUAL,SOL_USDC-PERPETUAL,LTC_USDC-PERPETUAL,MATIC_USDC-PERPETUAL,UNI_USDC-PERPETUAL,LINK_USDC-PERPETUAL,ALGO_USDC-PERPETUAL,NEAR_USDC-PERPETUAL,TRX_USDC-PERPETUAL,DOGE_USDC-PERPETUAL,BCH_USDC-PERPETUAL",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ },
+ "options": {
+ "assetEnabled": true,
+ "enabled": "SOL-21OCT22-20-C,SOL-21OCT22-20-P,SOL-21OCT22-24-C,SOL-21OCT22-24-P,SOL-21OCT22-25-C,SOL-21OCT22-25-P",
+ "available": "SOL-21OCT22-20-C,SOL-21OCT22-20-P,SOL-21OCT22-24-C,SOL-21OCT22-24-P,SOL-21OCT22-25-C,SOL-21OCT22-25-P,SOL-21OCT22-26-C,SOL-21OCT22-26-P,SOL-21OCT22-27-C,SOL-21OCT22-27-P,SOL-21OCT22-28-C,SOL-21OCT22-28-P,SOL-21OCT22-29-C,SOL-21OCT22-29-P,SOL-21OCT22-30-C,SOL-21OCT22-30-P,SOL-21OCT22-31-C,SOL-21OCT22-31-P,SOL-21OCT22-32-C,SOL-21OCT22-32-P,SOL-21OCT22-33-C,SOL-21OCT22-33-P,SOL-21OCT22-34-C,SOL-21OCT22-34-P,SOL-21OCT22-35-C,SOL-21OCT22-35-P,SOL-21OCT22-36-C,SOL-21OCT22-36-P,SOL-21OCT22-37-C,SOL-21OCT22-37-P,SOL-21OCT22-38-C,SOL-21OCT22-38-P,SOL-21OCT22-39-C,SOL-21OCT22-39-P,SOL-21OCT22-40-C,SOL-21OCT22-40-P,SOL-21OCT22-45-C,SOL-21OCT22-45-P,SOL-22OCT22-20-C,SOL-22OCT22-20-P,SOL-22OCT22-22-C,SOL-22OCT22-22-P,SOL-22OCT22-24-C,SOL-22OCT22-24-P,SOL-22OCT22-26-C,SOL-22OCT22-26-P,SOL-22OCT22-27-C,SOL-22OCT22-27-P,SOL-22OCT22-28-C,SOL-22OCT22-28-P,SOL-22OCT22-29-C,SOL-22OCT22-29-P,SOL-22OCT22-30-C,SOL-22OCT22-30-P,SOL-22OCT22-31-C,SOL-22OCT22-31-P,SOL-22OCT22-32-C,SOL-22OCT22-32-P,SOL-22OCT22-33-C,SOL-22OCT22-33-P,SOL-22OCT22-34-C,SOL-22OCT22-34-P,SOL-22OCT22-36-C,SOL-22OCT22-36-P,SOL-28OCT22-10-C,SOL-28OCT22-10-P,SOL-28OCT22-20-C,SOL-28OCT22-20-P,SOL-28OCT22-24-C,SOL-28OCT22-24-P,SOL-28OCT22-25-C,SOL-28OCT22-25-P,SOL-28OCT22-26-C,SOL-28OCT22-26-P,SOL-28OCT22-27-C,SOL-28OCT22-27-P,SOL-28OCT22-28-C,SOL-28OCT22-28-P,SOL-28OCT22-29-C,SOL-28OCT22-29-P,SOL-28OCT22-30-C,SOL-28OCT22-30-P,SOL-28OCT22-31-C,SOL-28OCT22-31-P,SOL-28OCT22-32-C,SOL-28OCT22-32-P,SOL-28OCT22-33-C,SOL-28OCT22-33-P,SOL-28OCT22-34-C,SOL-28OCT22-34-P,SOL-28OCT22-35-C,SOL-28OCT22-35-P,SOL-28OCT22-36-C,SOL-28OCT22-36-P,SOL-28OCT22-38-C,SOL-28OCT22-38-P,SOL-28OCT22-40-C,SOL-28OCT22-40-P,SOL-28OCT22-42-C,SOL-28OCT22-42-P,SOL-28OCT22-44-C,SOL-28OCT22-44-P,SOL-28OCT22-45-C,SOL-28OCT22-45-P,SOL-28OCT22-50-C,SOL-28OCT22-50-P,SOL-28OCT22-60-C,SOL-28OCT22-60-P,SOL-4NOV22-15-C,SOL-4NOV22-15-P,SOL-4NOV22-20-C,SOL-4NOV22-20-P,SOL-4NOV22-22-C,SOL-4NOV22-22-P,SOL-4NOV22-23-C,SOL-4NOV22-23-P,SOL-4NOV22-24-C,SOL-4NOV22-24-P,SOL-4NOV22-25-C,SOL-4NOV22-25-P,SOL-4NOV22-26-C,SOL-4NOV22-26-P,SOL-4NOV22-27-C,SOL-4NOV22-27-P,SOL-4NOV22-28-C,SOL-4NOV22-28-P,SOL-4NOV22-29-C,SOL-4NOV22-29-P,SOL-4NOV22-30-C,SOL-4NOV22-30-P,SOL-4NOV22-31-C,SOL-4NOV22-31-P,SOL-4NOV22-32-C,SOL-4NOV22-32-P,SOL-4NOV22-33-C,SOL-4NOV22-33-P,SOL-4NOV22-34-C,SOL-4NOV22-34-P,SOL-4NOV22-35-C,SOL-4NOV22-35-P,SOL-4NOV22-40-C,SOL-4NOV22-40-P,SOL-25NOV22-15-C,SOL-25NOV22-15-P,SOL-25NOV22-20-C,SOL-25NOV22-20-P,SOL-25NOV22-22-C,SOL-25NOV22-22-P,SOL-25NOV22-24-C,SOL-25NOV22-24-P,SOL-25NOV22-25-C,SOL-25NOV22-25-P,SOL-25NOV22-26-C,SOL-25NOV22-26-P,SOL-25NOV22-28-C,SOL-25NOV22-28-P,SOL-25NOV22-30-C,SOL-25NOV22-30-P,SOL-25NOV22-32-C,SOL-25NOV22-32-P,SOL-25NOV22-34-C,SOL-25NOV22-34-P,SOL-25NOV22-35-C,SOL-25NOV22-35-P,SOL-25NOV22-36-C,SOL-25NOV22-36-P,SOL-25NOV22-38-C,SOL-25NOV22-38-P,SOL-25NOV22-40-C,SOL-25NOV22-40-P,SOL-25NOV22-45-C,SOL-25NOV22-45-P,SOL-25NOV22-50-C,SOL-25NOV22-50-P,SOL-30DEC22-10-C,SOL-30DEC22-10-P,SOL-30DEC22-15-C,SOL-30DEC22-15-P,SOL-30DEC22-20-C,SOL-30DEC22-20-P,SOL-30DEC22-24-C,SOL-30DEC22-24-P,SOL-30DEC22-25-C,SOL-30DEC22-25-P,SOL-30DEC22-26-C,SOL-30DEC22-26-P,SOL-30DEC22-28-C,SOL-30DEC22-28-P,SOL-30DEC22-30-C,SOL-30DEC22-30-P,SOL-30DEC22-32-C,SOL-30DEC22-32-P,SOL-30DEC22-34-C,SOL-30DEC22-34-P,SOL-30DEC22-35-C,SOL-30DEC22-35-P,SOL-30DEC22-36-C,SOL-30DEC22-36-P,SOL-30DEC22-40-C,SOL-30DEC22-40-P,SOL-30DEC22-45-C,SOL-30DEC22-45-P,SOL-30DEC22-50-C,SOL-30DEC22-50-P,SOL-30DEC22-60-C,SOL-30DEC22-60-P,SOL-30DEC22-70-C,SOL-30DEC22-70-P,ETH-21OCT22-600-C,ETH-21OCT22-600-P,ETH-21OCT22-800-C,ETH-21OCT22-800-P,ETH-21OCT22-900-C,ETH-21OCT22-900-P,ETH-21OCT22-1000-C,ETH-21OCT22-1000-P,ETH-21OCT22-1100-C,ETH-21OCT22-1100-P,ETH-21OCT22-1150-C,ETH-21OCT22-1150-P,ETH-21OCT22-1200-C,ETH-21OCT22-1200-P,ETH-21OCT22-1225-C,ETH-21OCT22-1225-P,ETH-21OCT22-1250-C,ETH-21OCT22-1250-P,ETH-21OCT22-1275-C,ETH-21OCT22-1275-P,ETH-21OCT22-1300-C,ETH-21OCT22-1300-P,ETH-21OCT22-1325-C,ETH-21OCT22-1325-P,ETH-21OCT22-1350-C,ETH-21OCT22-1350-P,ETH-21OCT22-1400-C,ETH-21OCT22-1400-P,ETH-21OCT22-1450-C,ETH-21OCT22-1450-P,ETH-21OCT22-1500-C,ETH-21OCT22-1500-P,ETH-21OCT22-1600-C,ETH-21OCT22-1600-P,ETH-21OCT22-1700-C,ETH-21OCT22-1700-P,ETH-21OCT22-1800-C,ETH-21OCT22-1800-P,ETH-21OCT22-2000-C,ETH-21OCT22-2000-P,ETH-21OCT22-2200-C,ETH-21OCT22-2200-P,ETH-21OCT22-2400-C,ETH-21OCT22-2400-P,ETH-22OCT22-900-C,ETH-22OCT22-900-P,ETH-22OCT22-1000-C,ETH-22OCT22-1000-P,ETH-22OCT22-1100-C,ETH-22OCT22-1100-P,ETH-22OCT22-1150-C,ETH-22OCT22-1150-P,ETH-22OCT22-1200-C,ETH-22OCT22-1200-P,ETH-22OCT22-1225-C,ETH-22OCT22-1225-P,ETH-22OCT22-1250-C,ETH-22OCT22-1250-P,ETH-22OCT22-1275-C,ETH-22OCT22-1275-P,ETH-22OCT22-1300-C,ETH-22OCT22-1300-P,ETH-22OCT22-1325-C,ETH-22OCT22-1325-P,ETH-22OCT22-1350-C,ETH-22OCT22-1350-P,ETH-22OCT22-1375-C,ETH-22OCT22-1375-P,ETH-22OCT22-1400-C,ETH-22OCT22-1400-P,ETH-22OCT22-1450-C,ETH-22OCT22-1450-P,ETH-22OCT22-1500-C,ETH-22OCT22-1500-P,ETH-28OCT22-600-C,ETH-28OCT22-600-P,ETH-28OCT22-800-C,ETH-28OCT22-800-P,ETH-28OCT22-900-C,ETH-28OCT22-900-P,ETH-28OCT22-1000-C,ETH-28OCT22-1000-P,ETH-28OCT22-1100-C,ETH-28OCT22-1100-P,ETH-28OCT22-1150-C,ETH-28OCT22-1150-P,ETH-28OCT22-1200-C,ETH-28OCT22-1200-P,ETH-28OCT22-1250-C,ETH-28OCT22-1250-P,ETH-28OCT22-1300-C,ETH-28OCT22-1300-P,ETH-28OCT22-1350-C,ETH-28OCT22-1350-P,ETH-28OCT22-1400-C,ETH-28OCT22-1400-P,ETH-28OCT22-1450-C,ETH-28OCT22-1450-P,ETH-28OCT22-1500-C,ETH-28OCT22-1500-P,ETH-28OCT22-1550-C,ETH-28OCT22-1550-P,ETH-28OCT22-1600-C,ETH-28OCT22-1600-P,ETH-28OCT22-1650-C,ETH-28OCT22-1650-P,ETH-28OCT22-1700-C,ETH-28OCT22-1700-P,ETH-28OCT22-1750-C,ETH-28OCT22-1750-P,ETH-28OCT22-1800-C,ETH-28OCT22-1800-P,ETH-28OCT22-1850-C,ETH-28OCT22-1850-P,ETH-28OCT22-1900-C,ETH-28OCT22-1900-P,ETH-28OCT22-1950-C,ETH-28OCT22-1950-P,ETH-28OCT22-2000-C,ETH-28OCT22-2000-P,ETH-28OCT22-2050-C,ETH-28OCT22-2050-P,ETH-28OCT22-2100-C,ETH-28OCT22-2100-P,ETH-28OCT22-2200-C,ETH-28OCT22-2200-P,ETH-28OCT22-2300-C,ETH-28OCT22-2300-P,ETH-28OCT22-2400-C,ETH-28OCT22-2400-P,ETH-28OCT22-2500-C,ETH-28OCT22-2500-P,ETH-28OCT22-2600-C,ETH-28OCT22-2600-P,ETH-28OCT22-2700-C,ETH-28OCT22-2700-P,ETH-28OCT22-2800-C,ETH-28OCT22-2800-P,ETH-28OCT22-2900-C,ETH-28OCT22-2900-P,ETH-28OCT22-3000-C,ETH-28OCT22-3000-P,ETH-28OCT22-3100-C,ETH-28OCT22-3100-P,ETH-28OCT22-3200-C,ETH-28OCT22-3200-P,ETH-28OCT22-3400-C,ETH-28OCT22-3400-P,ETH-28OCT22-3600-C,ETH-28OCT22-3600-P,ETH-28OCT22-3800-C,ETH-28OCT22-3800-P,ETH-28OCT22-4000-C,ETH-28OCT22-4000-P,ETH-28OCT22-4200-C,ETH-28OCT22-4200-P,ETH-4NOV22-600-C,ETH-4NOV22-600-P,ETH-4NOV22-800-C,ETH-4NOV22-800-P,ETH-4NOV22-900-C,ETH-4NOV22-900-P,ETH-4NOV22-1000-C,ETH-4NOV22-1000-P,ETH-4NOV22-1050-C,ETH-4NOV22-1050-P,ETH-4NOV22-1100-C,ETH-4NOV22-1100-P,ETH-4NOV22-1150-C,ETH-4NOV22-1150-P,ETH-4NOV22-1200-C,ETH-4NOV22-1200-P,ETH-4NOV22-1250-C,ETH-4NOV22-1250-P,ETH-4NOV22-1300-C,ETH-4NOV22-1300-P,ETH-4NOV22-1350-C,ETH-4NOV22-1350-P,ETH-4NOV22-1400-C,ETH-4NOV22-1400-P,ETH-4NOV22-1450-C,ETH-4NOV22-1450-P,ETH-4NOV22-1500-C,ETH-4NOV22-1500-P,ETH-4NOV22-1600-C,ETH-4NOV22-1600-P,ETH-4NOV22-1800-C,ETH-4NOV22-1800-P,ETH-4NOV22-2000-C,ETH-4NOV22-2000-P,ETH-4NOV22-2200-C,ETH-4NOV22-2200-P,ETH-4NOV22-2400-C,ETH-4NOV22-2400-P,ETH-11NOV22-600-C,ETH-11NOV22-600-P,ETH-11NOV22-800-C,ETH-11NOV22-800-P,ETH-11NOV22-900-C,ETH-11NOV22-900-P,ETH-11NOV22-1000-C,ETH-11NOV22-1000-P,ETH-11NOV22-1100-C,ETH-11NOV22-1100-P,ETH-11NOV22-1150-C,ETH-11NOV22-1150-P,ETH-11NOV22-1200-C,ETH-11NOV22-1200-P,ETH-11NOV22-1250-C,ETH-11NOV22-1250-P,ETH-11NOV22-1300-C,ETH-11NOV22-1300-P,ETH-11NOV22-1350-C,ETH-11NOV22-1350-P,ETH-11NOV22-1400-C,ETH-11NOV22-1400-P,ETH-11NOV22-1500-C,ETH-11NOV22-1500-P,ETH-11NOV22-1600-C,ETH-11NOV22-1600-P,ETH-11NOV22-1800-C,ETH-11NOV22-1800-P,ETH-11NOV22-2000-C,ETH-11NOV22-2000-P,ETH-11NOV22-2200-C,ETH-11NOV22-2200-P,ETH-11NOV22-2400-C,ETH-11NOV22-2400-P,ETH-25NOV22-600-C,ETH-25NOV22-600-P,ETH-25NOV22-700-C,ETH-25NOV22-700-P,ETH-25NOV22-800-C,ETH-25NOV22-800-P,ETH-25NOV22-900-C,ETH-25NOV22-900-P,ETH-25NOV22-1000-C,ETH-25NOV22-1000-P,ETH-25NOV22-1050-C,ETH-25NOV22-1050-P,ETH-25NOV22-1100-C,ETH-25NOV22-1100-P,ETH-25NOV22-1150-C,ETH-25NOV22-1150-P,ETH-25NOV22-1200-C,ETH-25NOV22-1200-P,ETH-25NOV22-1250-C,ETH-25NOV22-1250-P,ETH-25NOV22-1300-C,ETH-25NOV22-1300-P,ETH-25NOV22-1350-C,ETH-25NOV22-1350-P,ETH-25NOV22-1400-C,ETH-25NOV22-1400-P,ETH-25NOV22-1450-C,ETH-25NOV22-1450-P,ETH-25NOV22-1500-C,ETH-25NOV22-1500-P,ETH-25NOV22-1600-C,ETH-25NOV22-1600-P,ETH-25NOV22-1700-C,ETH-25NOV22-1700-P,ETH-25NOV22-1800-C,ETH-25NOV22-1800-P,ETH-25NOV22-1900-C,ETH-25NOV22-1900-P,ETH-25NOV22-2000-C,ETH-25NOV22-2000-P,ETH-25NOV22-2100-C,ETH-25NOV22-2100-P,ETH-25NOV22-2200-C,ETH-25NOV22-2200-P,ETH-25NOV22-2300-C,ETH-25NOV22-2300-P,ETH-25NOV22-2400-C,ETH-25NOV22-2400-P,ETH-25NOV22-2500-C,ETH-25NOV22-2500-P,ETH-25NOV22-2600-C,ETH-25NOV22-2600-P,ETH-25NOV22-2700-C,ETH-25NOV22-2700-P,ETH-25NOV22-2800-C,ETH-25NOV22-2800-P,ETH-25NOV22-3000-C,ETH-25NOV22-3000-P,ETH-25NOV22-3200-C,ETH-25NOV22-3200-P,ETH-25NOV22-3400-C,ETH-25NOV22-3400-P,ETH-25NOV22-3600-C,ETH-25NOV22-3600-P,ETH-25NOV22-3800-C,ETH-25NOV22-3800-P,ETH-30DEC22-300-C,ETH-30DEC22-300-P,ETH-30DEC22-400-C,ETH-30DEC22-400-P,BTC-21OCT22-10000-C,BTC-21OCT22-10000-P,BTC-21OCT22-11000-C,BTC-21OCT22-11000-P,BTC-21OCT22-12000-C,BTC-21OCT22-12000-P,BTC-21OCT22-13000-C,BTC-21OCT22-13000-P,BTC-21OCT22-14000-C,BTC-21OCT22-14000-P,BTC-21OCT22-15000-C,BTC-21OCT22-15000-P,BTC-21OCT22-16000-C,BTC-21OCT22-16000-P,BTC-21OCT22-17000-C,BTC-21OCT22-17000-P,BTC-21OCT22-17500-C,BTC-21OCT22-17500-P,BTC-21OCT22-18000-C,BTC-21OCT22-18000-P,BTC-21OCT22-18500-C,BTC-21OCT22-18500-P,BTC-21OCT22-18750-C,BTC-21OCT22-18750-P,BTC-21OCT22-19000-C,BTC-21OCT22-19000-P,BTC-21OCT22-19250-C,BTC-21OCT22-19250-P,BTC-21OCT22-19500-C,BTC-21OCT22-19500-P,BTC-21OCT22-20000-C,BTC-21OCT22-20000-P,BTC-21OCT22-20500-C,BTC-21OCT22-20500-P,BTC-21OCT22-21000-C,BTC-21OCT22-21000-P,BTC-21OCT22-21500-C,BTC-21OCT22-21500-P,BTC-21OCT22-22000-C,BTC-21OCT22-22000-P,BTC-21OCT22-23000-C,BTC-21OCT22-23000-P,BTC-21OCT22-24000-C,BTC-21OCT22-24000-P",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ },
+ "option_combo": {
+ "assetEnabled": true,
+ "enabled": "BTC-STRG-21OCT22-19000_21000,BTC-CBUT-21OCT22-18500_19000_19500,BTC-CBUT-21OCT22-18000_19500_21000",
+ "available": "BTC-STRG-21OCT22-19000_21000,BTC-CBUT-21OCT22-18500_19000_19500,BTC-CBUT-21OCT22-18000_19500_21000,BTC-CBUT-21OCT22-19000_20000_21000,BTC-STRD-21OCT22-20000,BTC-STRD-21OCT22-19500,BTC-PS-21OCT22-19500_19000,BTC-CS-28OCT22-24000_32000,BTC-PCAL-25NOV22_28OCT22-12000,BTC-PS-28OCT22-19000_18000,BTC-ICOND-28OCT22-15000_16000_22000_23000,BTC-STRG-28OCT22-17500_20500,BTC-STRG-28OCT22-18000_20000,BTC-STRG-28OCT22-18500_20000,BTC-PS-28OCT22-20000_16000,BTC-PS-28OCT22-18000_17000,BTC-STRG-28OCT22-19000_22000,BTC-PBUT-4NOV22-17500_18500_19500,BTC-PSR13-11NOV22-14000_11000,BTC-PBUT-25NOV22-24000_30000_35000,BTC-STRG-25NOV22-14000_26000,BTC-CS-25NOV22-22000_23000,BTC-PSR12-25NOV22-18000_16000,BTC-STRG-25NOV22-19000_20000,BTC-STRG-25NOV22-16000_22000,BTC-CS-25NOV22-24000_26000,BTC-RR-25NOV22-18000_20000,BTC-PDIAG-30DEC22_25NOV22-16000_18000,BTC-PSR12-25NOV22-18000_17000,BTC-CS-25NOV22-20000_23000,BTC-PCAL-31MAR23_30DEC22-20000,BTC-CCAL-31MAR23_30DEC22-18000,BTC-CCAL-31MAR23_30DEC22-19000,BTC-CBUT-30DEC22-20000_25000_30000,BTC-STRD-30DEC22-20000,BTC-STRG-30DEC22-15000_28000,BTC-RR-30DEC22-18000_22000,BTC-PCAL-31MAR23_30DEC22-19000,BTC-PBUT111-30DEC22-12000_17000_19000,BTC-PS-30DEC22-12000_10000,BTC-PSR12-30DEC22-17000_16000,BTC-CBUT-31MAR23-22000_28000_34000,BTC-STRG-31MAR23-13000_38000,BTC-STRD-31MAR23-22000,BTC-PSR13-31MAR23-15000_14000,BTC-PSR13-31MAR23-25000_18000,BTC-PSR13-30JUN23-18000_15000,BTC-CBUT-29SEP23-20000_40000_60000,BTC-STRD-29SEP23-24000,ETH-PSR12-21OCT22-1250_1150,ETH-PSR12-21OCT22-1300_1200,ETH-STRG-21OCT22-1150_1450,ETH-PSR12-21OCT22-1200_1100,ETH-STRD-21OCT22-1500,ETH-STRG-21OCT22-1200_1400,ETH-PS-28OCT22-1500_1100,ETH-CS-28OCT22-1700_1950,ETH-PSR13-28OCT22-1200_1000,ETH-STRD-28OCT22-1350,ETH-CS-28OCT22-1400_1500,ETH-CS-28OCT22-1450_1550,ETH-PS-28OCT22-1000_900,ETH-CS-28OCT22-1400_1600,ETH-PCOND-28OCT22-900_1000_1400_1450,ETH-PSR12-28OCT22-1250_1100,ETH-IBUT-28OCT22-1250_1300_1350,ETH-PS-25NOV22-1400_1000,ETH-CSR13-25NOV22-1400_1800,ETH-CS-25NOV22-1500_2000,ETH-CS-25NOV22-1500_1700,ETH-CS-25NOV22-1500_1800,ETH-CBUT-25NOV22-1200_1300_1400,ETH-CBUT-25NOV22-1100_1300_1500,ETH-PSR12-25NOV22-1250_1100,ETH-CS-25NOV22-1300_1400,ETH-PBUT-25NOV22-1200_1300_1400,ETH-PSR12-25NOV22-1250_1150,ETH-CSR12-25NOV22-1300_1400,ETH-PSR12-30DEC22-1400_1200,ETH-STRD-30DEC22-1600,ETH-PCAL-31MAR23_30DEC22-2500,ETH-CS-30DEC22-1500_1900,ETH-CS-30DEC22-1700_1800,ETH-CS-30DEC22-1600_1800,ETH-PS-30DEC22-1300_1000,ETH-PDIAG-31MAR23_30DEC22-2500_3000,ETH-STRD-30DEC22-1400,ETH-STRD-30DEC22-1300,ETH-CS-30DEC22-1600_1900,ETH-CCAL-31MAR23_30DEC22-1300,ETH-CCAL-31MAR23_30DEC22-1200,ETH-CCAL-31MAR23_30DEC22-1400,ETH-PS-30DEC22-1500_1300,ETH-CBUT-30DEC22-1000_1300_2000,ETH-REV-31MAR23-1000,ETH-REV-31MAR23-1300,ETH-CS-31MAR23-1700_2000,ETH-STRG-31MAR23-800_3000,ETH-PS-31MAR23-1300_1000,SOL-PLAD-21OCT22-31_30_29,SOL-PS-28OCT22-32_28,SOL-RR-28OCT22-32_38,SOL-STRG-28OCT22-20_50,SOL-ICOND-28OCT22-20_25_45_50,SOL-PCAL-25NOV22_28OCT22-30,SOL-PSR12-28OCT22-30_29,SOL-CS-25NOV22-40_45",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ },
+ "future_combo": {
+ "assetEnabled": true,
+ "enabled": "SOL-FS-30DEC22_28OCT22,ETH-FS-28OCT22_PERP,ETH-FS-30DEC22_28OCT22,ETH-FS-30DEC22_PERP,ETH-FS-31MAR23_30DEC22",
+ "available": "SOL-FS-30DEC22_28OCT22,ETH-FS-28OCT22_PERP,ETH-FS-30DEC22_28OCT22,ETH-FS-30DEC22_PERP,ETH-FS-31MAR23_30DEC22,ETH-FS-29SEP23_30DEC22,ETH-FS-30JUN23_30DEC22,ETH-FS-31MAR23_PERP,ETH-FS-30JUN23_31MAR23,ETH-FS-30JUN23_PERP,ETH-FS-29SEP23_30JUN23,ETH-FS-29SEP23_PERP,BTC-FS-31MAR23_21OCT22,BTC-FS-21OCT22_PERP,BTC-FS-28OCT22_PERP,BTC-FS-30JUN23_28OCT22,BTC-FS-31MAR23_28OCT22,BTC-FS-29SEP23_28OCT22,BTC-FS-25NOV22_28OCT22,BTC-FS-29SEP23_25NOV22,BTC-FS-30DEC22_PERP,BTC-FS-30JUN23_30DEC22,BTC-FS-31MAR23_30DEC22,BTC-FS-29SEP23_30DEC22,BTC-FS-31MAR23_PERP,BTC-FS-30JUN23_31MAR23,BTC-FS-29SEP23_31MAR23,BTC-FS-30JUN23_PERP,BTC-FS-29SEP23_30JUN23,BTC-FS-29SEP23_PERP",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ }
+ }
+ },
+ "api": {
+ "authenticatedSupport": true,
+ "authenticatedWebsocketApiSupport": true,
+ "endpoints": {
+ "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
+ "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
+ "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
+ },
+ "credentials": {
+ "key": "Key",
+ "secret": "Secret"
+ },
+ "credentialsValidator": {
+ "requiresKey": true,
+ "requiresSecret": true
+ },
+ "urlEndpoints": {
+ "RestFuturesURL": "https://www.deribit.com",
+ "RestSpotURL": "https://test.deribit.com"
+ }
+ },
+ "features": {
+ "supports": {
+ "restAPI": true,
+ "restCapabilities": {
+ "autoPairUpdates": true
+ },
+ "websocketAPI": true,
+ "websocketCapabilities": {}
+ },
+ "enabled": {
+ "autoPairUpdates": true,
+ "websocketAPI": true,
+ "saveTradeData": false,
+ "tradeFeed": true,
+ "fillsFeed": false
+ }
+ },
+ "bankAccounts": [
+ {
+ "enabled": false,
+ "bankName": "",
+ "bankAddress": "",
+ "bankPostalCode": "",
+ "bankPostalCity": "",
+ "bankCountry": "",
+ "accountName": "",
+ "accountNumber": "",
+ "swiftCode": "",
+ "iban": "",
+ "supportedCurrencies": ""
+ }
+ ]
+ },
{
"name": "EXMO",
"enabled": true,
diff --git a/contrib/spellcheck/exclude_lines.txt b/contrib/spellcheck/exclude_lines.txt
index 4b30f4e3..4c7bbba8 100644
--- a/contrib/spellcheck/exclude_lines.txt
+++ b/contrib/spellcheck/exclude_lines.txt
@@ -21,3 +21,5 @@
currency.MIS: 0.002,
SHFT = NewCode("SHFT")
currency.SHFT: 84,
+ TotalIn float64 `json:"totalIn"`
+ TotalIn int64 `json:"totalIn"`
\ No newline at end of file
diff --git a/currency/code_types.go b/currency/code_types.go
index a52f5b53..e9a1f0c2 100644
--- a/currency/code_types.go
+++ b/currency/code_types.go
@@ -3070,6 +3070,7 @@ var (
WIF = NewCode("WIF")
AIDOGE = NewCode("AIDOGE")
PEPE = NewCode("PEPE")
+ EURR = NewCode("EURR")
stables = Currencies{
USDT,
diff --git a/docs/ADD_NEW_EXCHANGE.md b/docs/ADD_NEW_EXCHANGE.md
index ffea5800..47b5db79 100644
--- a/docs/ADD_NEW_EXCHANGE.md
+++ b/docs/ADD_NEW_EXCHANGE.md
@@ -206,6 +206,7 @@ Yes means supported, No means not yet implemented and NA means protocol unsuppor
| BTSE | Yes | Yes | NA |
| Bybit | Yes | Yes | NA |
| COINUT | Yes | Yes | NA |
+| Deribit | Yes | Yes | NA |
| Exmo | Yes | NA | NA |
| FTX | Yes | Yes | No | // <-------- new exchange
| CoinbasePro | Yes | Yes | No|
@@ -236,6 +237,7 @@ var Exchanges = []string{
"bybit",
"coinbasepro",
"coinut",
+ "deribit",
"exmo",
"ftx", // <-------- new exchange
"gateio",
diff --git a/docs/MULTICHAIN_TRANSFER_SUPPORT.md b/docs/MULTICHAIN_TRANSFER_SUPPORT.md
index e5607500..36af8711 100644
--- a/docs/MULTICHAIN_TRANSFER_SUPPORT.md
+++ b/docs/MULTICHAIN_TRANSFER_SUPPORT.md
@@ -55,6 +55,7 @@ $ ./gctcli withdrawcryptofunds --exchange=binance --currency=USDT --address=TJU9
| Bybit | Yes | Yes | |
| CoinbasePro | No | No | No|
| COINUT | No | No | NA |
+| Deribit | Yes | Yes | |
| Exmo | Yes | Yes | Addresses must be created via their website first |
| GateIO | Yes | Yes | |
| Gemini | No | No | |
diff --git a/docs/OHLCV.md b/docs/OHLCV.md
index e140af46..b10e4ec7 100644
--- a/docs/OHLCV.md
+++ b/docs/OHLCV.md
@@ -76,7 +76,8 @@ A helper tool [cmd/dbseed](../cmd/dbseed/README.md) has been created for assisti
| BTSE | Y |
| Bybit | Y |
| Coinbase Pro | Y |
-| Coinut | |
+| Coinut | |
+| Deribit | Y |
| Exmo | |
| GateIO | Y |
| Gemini | |
diff --git a/engine/helpers.go b/engine/helpers.go
index 4dd95a15..47d5c71a 100644
--- a/engine/helpers.go
+++ b/engine/helpers.go
@@ -40,6 +40,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/coinbasepro"
"github.com/thrasher-corp/gocryptotrader/exchanges/coinut"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/deribit"
"github.com/thrasher-corp/gocryptotrader/exchanges/exmo"
"github.com/thrasher-corp/gocryptotrader/exchanges/gateio"
"github.com/thrasher-corp/gocryptotrader/exchanges/gemini"
@@ -1015,6 +1016,8 @@ func NewSupportedExchangeByName(name string) (exchange.IBotExchange, error) {
return new(bybit.Bybit), nil
case "coinut":
return new(coinut.COINUT), nil
+ case "deribit":
+ return new(deribit.Deribit), nil
case "exmo":
return new(exmo.EXMO), nil
case "coinbasepro":
diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go
index ef8a749a..f5174fa0 100644
--- a/exchanges/asset/asset.go
+++ b/exchanges/asset/asset.go
@@ -41,12 +41,15 @@ const (
USDTMarginedFutures
USDCMarginedFutures
Options
+ OptionCombo
+ FutureCombo
// Added to represent a USDT and USDC based linear derivatives(futures/perpetual) assets in Bybit V5.
LinearContract
- futuresFlag = PerpetualContract | PerpetualSwap | Futures | DeliveryFutures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures | LinearContract
- supportedFlag = Spot | Margin | CrossMargin | MarginFunding | Index | Binary | PerpetualContract | PerpetualSwap | Futures | DeliveryFutures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures | Options | LinearContract
+ optionsFlag = OptionCombo | Options
+ futuresFlag = PerpetualContract | PerpetualSwap | Futures | DeliveryFutures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures | LinearContract | FutureCombo
+ supportedFlag = Spot | Margin | CrossMargin | MarginFunding | Index | Binary | PerpetualContract | PerpetualSwap | Futures | DeliveryFutures | UpsideProfitContract | DownsideProfitContract | CoinMarginedFutures | USDTMarginedFutures | USDCMarginedFutures | Options | LinearContract | OptionCombo | FutureCombo
spot = "spot"
margin = "margin"
@@ -65,10 +68,12 @@ const (
usdtMarginedFutures = "usdtmarginedfutures"
usdcMarginedFutures = "usdcmarginedfutures"
options = "options"
+ optionCombo = "option_combo"
+ futureCombo = "future_combo"
)
var (
- supportedList = Items{Spot, Margin, CrossMargin, MarginFunding, Index, Binary, PerpetualContract, PerpetualSwap, Futures, DeliveryFutures, UpsideProfitContract, DownsideProfitContract, CoinMarginedFutures, USDTMarginedFutures, USDCMarginedFutures, Options, LinearContract}
+ supportedList = Items{Spot, Margin, CrossMargin, MarginFunding, Index, Binary, PerpetualContract, PerpetualSwap, Futures, DeliveryFutures, UpsideProfitContract, DownsideProfitContract, CoinMarginedFutures, USDTMarginedFutures, USDCMarginedFutures, Options, LinearContract, OptionCombo, FutureCombo}
)
// Supported returns a list of supported asset types
@@ -111,6 +116,10 @@ func (a Item) String() string {
return usdcMarginedFutures
case Options:
return options
+ case OptionCombo:
+ return optionCombo
+ case FutureCombo:
+ return futureCombo
default:
return ""
}
@@ -212,6 +221,10 @@ func New(input string) (Item, error) {
return USDCMarginedFutures, nil
case options, "option":
return Options, nil
+ case optionCombo:
+ return OptionCombo, nil
+ case futureCombo:
+ return FutureCombo, nil
default:
return 0, fmt.Errorf("%w '%v', only supports %s",
ErrNotSupported,
@@ -229,3 +242,8 @@ func UseDefault() Item {
func (a Item) IsFutures() bool {
return a != Empty && futuresFlag&a == a
}
+
+// IsOptions checks if the asset type is options contract based asset
+func (a Item) IsOptions() bool {
+ return a != Empty && optionsFlag&a == a
+}
diff --git a/exchanges/asset/asset_test.go b/exchanges/asset/asset_test.go
index 13105dc1..c66126a4 100644
--- a/exchanges/asset/asset_test.go
+++ b/exchanges/asset/asset_test.go
@@ -93,6 +93,11 @@ func TestNew(t *testing.T) {
{Input: "CoinMarginedFutures", Expected: CoinMarginedFutures},
{Input: "USDTMarginedFutures", Expected: USDTMarginedFutures},
{Input: "USDCMarginedFutures", Expected: USDCMarginedFutures},
+ {Input: "Options", Expected: Options},
+ {Input: "Option", Expected: Options},
+ {Input: "Future", Error: ErrNotSupported},
+ {Input: "future_combo", Expected: FutureCombo},
+ {Input: "option_combo", Expected: OptionCombo},
}
for x := range cases {
@@ -181,6 +186,9 @@ func TestIsFutures(t *testing.T) {
{
item: USDCMarginedFutures,
isFutures: true,
+ }, {
+ item: FutureCombo,
+ isFutures: true,
},
}
for _, s := range scenarios {
@@ -194,6 +202,40 @@ func TestIsFutures(t *testing.T) {
}
}
+func TestIsOptions(t *testing.T) {
+ t.Parallel()
+ type scenario struct {
+ item Item
+ isOptions bool
+ }
+ scenarios := []scenario{
+ {
+ item: Options,
+ isOptions: true,
+ }, {
+ item: OptionCombo,
+ isOptions: true,
+ },
+ {
+ item: Futures,
+ isOptions: false,
+ },
+ {
+ item: Empty,
+ isOptions: false,
+ },
+ }
+ for _, s := range scenarios {
+ testScenario := s
+ t.Run(testScenario.item.String(), func(t *testing.T) {
+ t.Parallel()
+ if testScenario.item.IsOptions() != testScenario.isOptions {
+ t.Errorf("expected %v isOptions to be %v", testScenario.item, testScenario.isOptions)
+ }
+ })
+ }
+}
+
func TestUnmarshalMarshal(t *testing.T) {
t.Parallel()
data, err := json.Marshal(Item(0))
diff --git a/exchanges/deribit/README.md b/exchanges/deribit/README.md
new file mode 100644
index 00000000..c57cbd7a
--- /dev/null
+++ b/exchanges/deribit/README.md
@@ -0,0 +1,140 @@
+# GoCryptoTrader package Deribit
+
+
+
+
+[](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml)
+[](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE)
+[](https://godoc.org/github.com/thrasher-corp/gocryptotrader/exchanges/deribit)
+[](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master)
+[](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader)
+
+
+This deribit 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)
+
+## Deribit 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 d exchange.IBotExchange
+
+for i := range bot.Exchanges {
+ if bot.Exchanges[i].GetName() == "Deribit" {
+ d = bot.Exchanges[i]
+ }
+}
+
+// Public calls - wrapper functions
+
+// Fetches current ticker information
+tick, err := d.FetchTicker()
+if err != nil {
+ // Handle error
+}
+
+// Fetches current orderbook information
+ob, err := d.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 := d.GetAccountInfo()
+if err != nil {
+ // Handle error
+}
+```
+
++ If enabled via individually importing package, rudimentary example below:
+
+```go
+// Public calls
+
+// Fetches current ticker information
+tick, err := d.GetTicker()
+if err != nil {
+ // Handle error
+}
+
+// Fetches current orderbook information
+ob, err := d.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 := d.GetUserInfo(...)
+if err != nil {
+ // Handle error
+}
+
+// Submits an order and the exchange and returns its tradeID
+tradeID, err := d.Trade(...)
+if err != nil {
+ // Handle error
+}
+```
+
+### How to do Websocket public/private calls
+
+```go
+ // Exchanges will be abstracted out in further updates and examples will be
+ // supplied then
+```
+
+### 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
+
+
+
+If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
+
+***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc***
diff --git a/exchanges/deribit/deribit.go b/exchanges/deribit/deribit.go
new file mode 100644
index 00000000..00efc5ab
--- /dev/null
+++ b/exchanges/deribit/deribit.go
@@ -0,0 +1,2795 @@
+package deribit
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/common/crypto"
+ "github.com/thrasher-corp/gocryptotrader/currency"
+ exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/asset"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/nonce"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/order"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/request"
+)
+
+// Deribit is the overarching type across this package
+type Deribit struct {
+ exchange.Base
+}
+
+const (
+ deribitAPIVersion = "/api/v2"
+
+ // Public endpoints
+
+ // Market Data
+ getBookByCurrency = "public/get_book_summary_by_currency"
+ getBookByInstrument = "public/get_book_summary_by_instrument"
+ getContractSize = "public/get_contract_size"
+ getCurrencies = "public/get_currencies"
+ getDeliveryPrices = "public/get_delivery_prices"
+ getFundingChartData = "public/get_funding_chart_data"
+ getFundingRateHistory = "public/get_funding_rate_history"
+ getFundingRateValue = "public/get_funding_rate_value"
+ getHistoricalVolatility = "public/get_historical_volatility"
+ getCurrencyIndexPrice = "public/get_index"
+ getIndexPrice = "public/get_index_price"
+ getIndexPriceNames = "public/get_index_price_names"
+ getInstrument = "public/get_instrument"
+ getInstruments = "public/get_instruments"
+ getLastSettlementsByCurrency = "public/get_last_settlements_by_currency"
+ getLastSettlementsByInstrument = "public/get_last_settlements_by_instrument"
+ getLastTradesByCurrency = "public/get_last_trades_by_currency"
+ getLastTradesByCurrencyAndTime = "public/get_last_trades_by_currency_and_time"
+ getLastTradesByInstrument = "public/get_last_trades_by_instrument"
+ getLastTradesByInstrumentAndTime = "public/get_last_trades_by_instrument_and_time"
+ getMarkPriceHistory = "public/get_mark_price_history"
+ getOrderbook = "public/get_order_book"
+ getOrderbookByInstrumentID = "public/get_order_book_by_instrument_id"
+ getRFQ = "public/get_rfqs"
+ getTradeVolumes = "public/get_trade_volumes"
+ getTradingViewChartData = "public/get_tradingview_chart_data"
+ getVolatilityIndex = "public/get_volatility_index_data"
+ getTicker = "public/ticker"
+
+ // Authenticated endpoints
+
+ // wallet eps
+ cancelTransferByID = "private/cancel_transfer_by_id"
+ cancelWithdrawal = "private/cancel_withdrawal"
+ createDepositAddress = "private/create_deposit_address"
+ getCurrentDepositAddress = "private/get_current_deposit_address"
+ getDeposits = "private/get_deposits"
+ getTransfers = "private/get_transfers"
+ getWithdrawals = "private/get_withdrawals"
+ submitTransferBetweenSubAccounts = "private/submit_transfer_between_subaccounts"
+ submitTransferToSubaccount = "private/submit_transfer_to_subaccount"
+ submitTransferToUser = "private/submit_transfer_to_user"
+ submitWithdraw = "private/withdraw"
+
+ // trading endpoints
+ submitBuy = "private/buy"
+ submitSell = "private/sell"
+ submitEdit = "private/edit"
+ editByLabel = "private/edit_by_label"
+ submitCancel = "private/cancel"
+ submitCancelAll = "private/cancel_all"
+ submitCancelAllByCurrency = "private/cancel_all_by_currency"
+ submitCancelAllByKind = "private/cancel_all_by_kind_or_type"
+ submitCancelAllByInstrument = "private/cancel_all_by_instrument"
+ submitCancelByLabel = "private/cancel_by_label"
+ submitCancelQuotes = "private/cancel_quotes"
+ submitClosePosition = "private/close_position"
+ getMargins = "private/get_margins"
+ getMMPConfig = "private/get_mmp_config"
+ getOpenOrdersByCurrency = "private/get_open_orders_by_currency"
+ getOpenOrdersByLabel = "private/get_open_orders_by_label"
+ getOpenOrdersByInstrument = "private/get_open_orders_by_instrument"
+ getOrderHistoryByCurrency = "private/get_order_history_by_currency"
+ getOrderHistoryByInstrument = "private/get_order_history_by_instrument"
+ getOrderMarginByIDs = "private/get_order_margin_by_ids"
+ getOrderState = "private/get_order_state"
+ getOrderStateByLabel = "private/get_order_state_by_label"
+ getTriggerOrderHistory = "private/get_trigger_order_history"
+ getUserTradesByCurrency = "private/get_user_trades_by_currency"
+ getUserTradesByCurrencyAndTime = "private/get_user_trades_by_currency_and_time"
+ getUserTradesByInstrument = "private/get_user_trades_by_instrument"
+ getUserTradesByInstrumentAndTime = "private/get_user_trades_by_instrument_and_time"
+ getUserTradesByOrder = "private/get_user_trades_by_order"
+ resetMMP = "private/reset_mmp"
+ sendRFQ = "private/send_rfq"
+ setMMPConfig = "private/set_mmp_config"
+ getSettlementHistoryByInstrument = "private/get_settlement_history_by_instrument"
+ getSettlementHistoryByCurrency = "private/get_settlement_history_by_currency"
+
+ // account management eps
+ getAnnouncements = "public/get_announcements"
+ getPublicPortfolioMargins = "public/get_portfolio_margins"
+ changeAPIKeyName = "private/change_api_key_name"
+ changeMarginModel = "private/change_margin_model"
+ changeScopeInAPIKey = "private/change_scope_in_api_key"
+ changeSubAccountName = "private/change_subaccount_name"
+ createAPIKey = "private/create_api_key"
+ createSubAccount = "private/create_subaccount"
+ disableAPIKey = "private/disable_api_key"
+ editAPIKey = "private/edit_api_key"
+ enableAffiliateProgram = "private/enable_affiliate_program"
+ enableAPIKey = "private/enable_api_key"
+ getAccessLog = "private/get_access_log"
+ getAccountSummary = "private/get_account_summary"
+ getAffiliateProgramInfo = "private/get_affiliate_program_info"
+ getEmailLanguage = "private/get_email_language"
+ getNewAnnouncements = "private/get_new_announcements"
+ getPrivatePortfolioMargins = "private/get_portfolio_margins"
+ getPosition = "private/get_position"
+ getPositions = "private/get_positions"
+ getSubAccounts = "private/get_subaccounts"
+ getSubAccountDetails = "private/get_subaccounts_details"
+ getTransactionLog = "private/get_transaction_log"
+ getUserLocks = "private/get_user_locks"
+ listAPIKeys = "private/list_api_keys"
+ listCustodyAccounts = "private/list_custody_accounts"
+ removeAPIKey = "private/remove_api_key"
+ removeSubAccount = "private/remove_subaccount"
+ resetAPIKey = "private/reset_api_key"
+ setAnnouncementAsRead = "private/set_announcement_as_read"
+ setEmailForSubAccount = "private/set_email_for_subaccount"
+ setEmailLanguage = "private/set_email_language"
+ setSelfTradingConfig = "private/set_self_trading_config"
+ toggleNotificationsFromSubAccount = "private/toggle_notifications_from_subaccount"
+ togglePortfolioMargining = "private/toggle_portfolio_margining"
+ toggleSubAccountLogin = "private/toggle_subaccount_login"
+
+ // Combo Books Endpoints
+ getComboDetails = "public/get_combo_details"
+ getComboIDs = "public/get_combo_ids"
+ getCombos = "public/get_combos"
+ createCombos = "private/create_combo"
+
+ // Block Trades Endpoints
+ executeBlockTrades = "private/execute_block_trade"
+ getBlockTrades = "private/get_block_trade"
+ getLastBlockTradesByCurrency = "private/get_last_block_trades_by_currency"
+ invalidateBlockTradesSignature = "private/invalidate_block_trade_signature"
+ movePositions = "private/move_positions"
+ simulateBlockPosition = "private/simulate_block_trade"
+ verifyBlockTrades = "private/verify_block_trade"
+
+ // roles
+
+ roleMaker = "maker"
+ roleTaker = "taker"
+)
+
+// GetBookSummaryByCurrency gets book summary data for currency requested
+func (d *Deribit) GetBookSummaryByCurrency(ctx context.Context, ccy currency.Code, kind string) ([]BookSummaryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ var resp []BookSummaryData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getBookByCurrency, params), &resp)
+}
+
+// GetBookSummaryByInstrument gets book summary data for instrument requested
+func (d *Deribit) GetBookSummaryByInstrument(ctx context.Context, instrument string) ([]BookSummaryData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ var resp []BookSummaryData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getBookByInstrument, params), &resp)
+}
+
+// GetContractSize gets contract size for instrument requested
+func (d *Deribit) GetContractSize(ctx context.Context, instrument string) (*ContractSizeData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ var resp *ContractSizeData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getContractSize, params), &resp)
+}
+
+// GetCurrencies gets all cryptocurrencies supported by the API
+func (d *Deribit) GetCurrencies(ctx context.Context) ([]CurrencyData, error) {
+ var resp []CurrencyData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, getCurrencies, &resp)
+}
+
+// GetDeliveryPrices gets all delivery prices for the given inde name
+func (d *Deribit) GetDeliveryPrices(ctx context.Context, indexName string, offset, count int64) (*IndexDeliveryPrice, error) {
+ if indexName == "" {
+ return nil, errUnsupportedIndexName
+ }
+ params := url.Values{}
+ params.Set("index_name", indexName)
+ if offset > 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ var resp *IndexDeliveryPrice
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getDeliveryPrices, params), &resp)
+}
+
+// GetFundingChartData gets funding chart data for the requested instrument and time length
+// supported lengths: 8h, 24h, 1m <-(1month)
+func (d *Deribit) GetFundingChartData(ctx context.Context, instrument, length string) (*FundingChartData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("length", length)
+ var resp *FundingChartData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getFundingChartData, params), &resp)
+}
+
+// GetFundingRateHistory retrieves hourly historical interest rate for requested PERPETUAL instrument.
+func (d *Deribit) GetFundingRateHistory(ctx context.Context, instrumentName string, startTime, endTime time.Time) ([]FundingRateHistory, error) {
+ params, err := checkInstrument(instrumentName)
+ if err != nil {
+ return nil, err
+ }
+ err = common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ var resp []FundingRateHistory
+ return resp, d.SendHTTPRequest(ctx, exchange.RestSpot, nonMatchingEPL, common.EncodeURLValues(getFundingRateHistory, params), &resp)
+}
+
+// GetFundingRateValue gets funding rate value data.
+func (d *Deribit) GetFundingRateValue(ctx context.Context, instrument string, startTime, endTime time.Time) (float64, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return 0, err
+ }
+ err = common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return 0, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ var resp float64
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getFundingRateValue, params), &resp)
+}
+
+// GetHistoricalVolatility gets historical volatility data
+func (d *Deribit) GetHistoricalVolatility(ctx context.Context, ccy currency.Code) ([]HistoricalVolatilityData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var data [][2]interface{}
+ err := d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getHistoricalVolatility, params), &data)
+ if err != nil {
+ return nil, err
+ }
+ resp := make([]HistoricalVolatilityData, len(data))
+ for x := range data {
+ timeData, ok := data[x][0].(float64)
+ if !ok {
+ return resp, common.GetTypeAssertError("float64", data[x][0], "time data")
+ }
+ val, ok := data[x][1].(float64)
+ if !ok {
+ return resp, common.GetTypeAssertError("float64", data[x][1], "volatility value")
+ }
+ resp[x] = HistoricalVolatilityData{
+ Timestamp: timeData,
+ Value: val,
+ }
+ }
+ return resp, nil
+}
+
+// GetCurrencyIndexPrice retrieves the current index price for the instruments, for the selected currency.
+func (d *Deribit) GetCurrencyIndexPrice(ctx context.Context, ccy currency.Code) (*IndexPrice, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp *IndexPrice
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getCurrencyIndexPrice, params), &resp)
+}
+
+// GetIndexPrice gets price data for the requested index
+func (d *Deribit) GetIndexPrice(ctx context.Context, index string) (*IndexPriceData, error) {
+ if index == "" {
+ return nil, errUnsupportedIndexName
+ }
+ params := url.Values{}
+ params.Set("index_name", index)
+ var resp *IndexPriceData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getIndexPrice, params), &resp)
+}
+
+// GetIndexPriceNames gets names of indexes
+func (d *Deribit) GetIndexPriceNames(ctx context.Context) ([]string, error) {
+ var resp []string
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, getIndexPriceNames, &resp)
+}
+
+// GetInstrument retrieves instrument detail
+func (d *Deribit) GetInstrument(ctx context.Context, instrument string) (*InstrumentData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ var resp *InstrumentData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestSpot, nonMatchingEPL,
+ common.EncodeURLValues(getInstrument, params), &resp)
+}
+
+// GetInstruments gets data for all available instruments
+func (d *Deribit) GetInstruments(ctx context.Context, ccy currency.Code, kind string, expired bool) ([]InstrumentData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if expired {
+ params.Set("expired", "true")
+ }
+ var resp []InstrumentData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getInstruments, params), &resp)
+}
+
+// GetLastSettlementsByCurrency gets last settlement data by currency
+func (d *Deribit) GetLastSettlementsByCurrency(ctx context.Context, ccy currency.Code, settlementType, continuation string, count int64, searchStartTime time.Time) (*SettlementsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if settlementType != "" {
+ params.Set("type", settlementType)
+ }
+ if continuation != "" {
+ params.Set("continuation", continuation)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if !searchStartTime.IsZero() {
+ params.Set("search_start_timestamp", strconv.FormatInt(searchStartTime.UnixMilli(), 10))
+ }
+ var resp *SettlementsData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getLastSettlementsByCurrency, params), &resp)
+}
+
+// GetLastSettlementsByInstrument gets last settlement data for requested instrument
+func (d *Deribit) GetLastSettlementsByInstrument(ctx context.Context, instrument, settlementType, continuation string, count int64, startTime time.Time) (*SettlementsData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if settlementType != "" {
+ params.Set("type", settlementType)
+ }
+ if continuation != "" {
+ params.Set("continuation", continuation)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if !startTime.IsZero() {
+ params.Set("search_start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ }
+ var resp *SettlementsData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getLastSettlementsByInstrument, params), &resp)
+}
+
+// GetLastTradesByCurrency gets last trades for requested currency
+func (d *Deribit) GetLastTradesByCurrency(ctx context.Context, ccy currency.Code, kind, startID, endID, sorting string, count int64, includeOld bool) (*PublicTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if startID != "" {
+ params.Set("start_id", startID)
+ }
+ if endID != "" {
+ params.Set("end_id", endID)
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if includeOld {
+ params.Set("include_old", "true")
+ }
+ var resp *PublicTradesData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getLastTradesByCurrency, params), &resp)
+}
+
+// GetLastTradesByCurrencyAndTime gets last trades for requested currency and time intervals
+func (d *Deribit) GetLastTradesByCurrencyAndTime(ctx context.Context, ccy currency.Code, kind, sorting string, count int64, startTime, endTime time.Time) (*PublicTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ err := common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ var resp *PublicTradesData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getLastTradesByCurrencyAndTime, params), &resp)
+}
+
+// GetLastTradesByInstrument gets last trades for requested instrument requested
+func (d *Deribit) GetLastTradesByInstrument(ctx context.Context, instrument, startSeq, endSeq, sorting string, count int64, includeOld bool) (*PublicTradesData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if startSeq != "" {
+ params.Set("start_seq", startSeq)
+ }
+ if endSeq != "" {
+ params.Set("end_seq", endSeq)
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if includeOld {
+ params.Set("include_old", "true")
+ }
+ var resp *PublicTradesData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getLastTradesByInstrument, params), &resp)
+}
+
+// GetLastTradesByInstrumentAndTime gets last trades for requested instrument requested and time intervals
+func (d *Deribit) GetLastTradesByInstrumentAndTime(ctx context.Context, instrument, sorting string, count int64, startTime, endTime time.Time) (*PublicTradesData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ err = common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ var resp *PublicTradesData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getLastTradesByInstrumentAndTime, params), &resp)
+}
+
+// GetMarkPriceHistory gets data for mark price history
+func (d *Deribit) GetMarkPriceHistory(ctx context.Context, instrument string, startTime, endTime time.Time) ([]MarkPriceHistory, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ err = common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ var resp []MarkPriceHistory
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getMarkPriceHistory, params), &resp)
+}
+
+func checkInstrument(instrumentName string) (url.Values, error) {
+ if instrumentName == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ params := url.Values{}
+ params.Set("instrument_name", instrumentName)
+ return params, nil
+}
+
+// GetOrderbook gets data orderbook of requested instrument
+func (d *Deribit) GetOrderbook(ctx context.Context, instrument string, depth int64) (*Orderbook, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if depth != 0 {
+ params.Set("depth", strconv.FormatInt(depth, 10))
+ }
+ var resp *Orderbook
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getOrderbook, params), &resp)
+}
+
+// GetOrderbookByInstrumentID retrieves orderbook by instrument ID
+func (d *Deribit) GetOrderbookByInstrumentID(ctx context.Context, instrumentID int64, depth float64) (*Orderbook, error) {
+ if instrumentID == 0 {
+ return nil, errInvalidInstrumentID
+ }
+ params := url.Values{}
+ params.Set("instrument_id", strconv.FormatInt(instrumentID, 10))
+ if depth != 0 {
+ params.Set("depth", strconv.FormatFloat(depth, 'f', -1, 64))
+ }
+ var resp *Orderbook
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getOrderbookByInstrumentID, params), &resp)
+}
+
+// GetSupportedIndexNames retrieves the identifiers of all supported Price Indexes
+// 'type' represents Type of a cryptocurrency price index. possible 'all', 'spot', 'derivative'
+func (d *Deribit) GetSupportedIndexNames(ctx context.Context, priceIndexType string) ([]string, error) {
+ params := url.Values{}
+ if priceIndexType != "" {
+ params.Set("type", priceIndexType)
+ }
+ var resp []string
+ return resp, d.SendHTTPRequest(ctx, exchange.RestSpot, nonMatchingEPL, common.EncodeURLValues("public/get_supported_index_names", params), &resp)
+}
+
+// GetRequestForQuote retrieves RFQ information.
+func (d *Deribit) GetRequestForQuote(ctx context.Context, ccy currency.Code, kind string) ([]RequestForQuote, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ var resp []RequestForQuote
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getRFQ, params), &resp)
+}
+
+// GetTradeVolumes gets trade volumes' data of all instruments
+func (d *Deribit) GetTradeVolumes(ctx context.Context, extended bool) ([]TradeVolumesData, error) {
+ params := url.Values{}
+ if extended {
+ params.Set("extended", "true")
+ }
+ var resp []TradeVolumesData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getTradeVolumes, params), &resp)
+}
+
+// GetTradingViewChart gets volatility index data for the requested instrument
+func (d *Deribit) GetTradingViewChart(ctx context.Context, instrument, resolution string, startTime, endTime time.Time) (*TVChartData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ err = common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ if resolution == "" {
+ return nil, errResolutionNotSet
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ params.Set("resolution", resolution)
+ var resp *TVChartData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getTradingViewChartData, params), &resp)
+}
+
+// GetResolutionFromInterval returns the string representation of intervals given kline.Interval instance.
+func (d *Deribit) GetResolutionFromInterval(interval kline.Interval) (string, error) {
+ switch interval {
+ case kline.HundredMilliseconds:
+ return "100ms", nil
+ case kline.OneMin:
+ return "1", nil
+ case kline.ThreeMin:
+ return "3", nil
+ case kline.FiveMin:
+ return "5", nil
+ case kline.TenMin:
+ return "10", nil
+ case kline.FifteenMin:
+ return "15", nil
+ case kline.ThirtyMin:
+ return "30", nil
+ case kline.OneHour:
+ return "60", nil
+ case kline.TwoHour:
+ return "120", nil
+ case kline.ThreeHour:
+ return "180", nil
+ case kline.SixHour:
+ return "360", nil
+ case kline.TwelveHour:
+ return "720", nil
+ case kline.OneDay:
+ return "1D", nil
+ case kline.Raw:
+ return interval.Word(), nil
+ default:
+ return "", kline.ErrUnsupportedInterval
+ }
+}
+
+// GetVolatilityIndex gets volatility index for the requested currency
+func (d *Deribit) GetVolatilityIndex(ctx context.Context, ccy currency.Code, resolution string, startTime, endTime time.Time) ([]VolatilityIndexData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ err := common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ if resolution == "" {
+ return nil, errResolutionNotSet
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ params.Set("resolution", resolution)
+ var resp VolatilityIndexRawData
+ err = d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL,
+ common.EncodeURLValues(getVolatilityIndex, params), &resp)
+ if err != nil {
+ return nil, err
+ }
+ response := make([]VolatilityIndexData, len(resp.Data))
+ for x := range resp.Data {
+ response[x] = VolatilityIndexData{
+ TimestampMS: time.UnixMilli(int64(resp.Data[x][0])),
+ Open: resp.Data[x][1],
+ High: resp.Data[x][2],
+ Low: resp.Data[x][3],
+ Close: resp.Data[x][4],
+ }
+ }
+ return response, nil
+}
+
+// GetPublicTicker gets public ticker data of the instrument requested
+func (d *Deribit) GetPublicTicker(ctx context.Context, instrument string) (*TickerData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ var resp *TickerData
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getTicker, params), &resp)
+}
+
+// SendHTTPRequest sends an unauthenticated HTTP request
+func (d *Deribit) SendHTTPRequest(ctx context.Context, ep exchange.URL, epl request.EndpointLimit, path string, result interface{}) error {
+ endpoint, err := d.API.Endpoints.GetURL(ep)
+ if err != nil {
+ return err
+ }
+ data := &struct {
+ JSONRPC string `json:"jsonrpc"`
+ ID int64 `json:"id"`
+ Data interface{} `json:"result"`
+ }{
+ Data: result,
+ }
+ return d.SendPayload(ctx, epl, func() (*request.Item, error) {
+ return &request.Item{
+ Method: http.MethodGet,
+ Path: endpoint + deribitAPIVersion + "/" + path,
+ Result: data,
+ Verbose: d.Verbose,
+ HTTPDebugging: d.HTTPDebugging,
+ HTTPRecording: d.HTTPRecording,
+ }, nil
+ }, request.UnauthenticatedRequest)
+}
+
+// GetAccountSummary gets account summary data for the requested instrument
+func (d *Deribit) GetAccountSummary(ctx context.Context, ccy currency.Code, extended bool) (*AccountSummaryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if extended {
+ params.Set("extended", "true")
+ }
+ var resp *AccountSummaryData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getAccountSummary, params, &resp)
+}
+
+// CancelWithdrawal cancels withdrawal request for a given currency by its id
+func (d *Deribit) CancelWithdrawal(ctx context.Context, ccy currency.Code, id int64) (*CancelWithdrawalData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, withdrawal id has to be positive integer", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ params.Set("id", strconv.FormatInt(id, 10))
+ var resp *CancelWithdrawalData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ cancelWithdrawal, params, &resp)
+}
+
+// CancelTransferByID cancels transfer by ID through the websocket connection.
+func (d *Deribit) CancelTransferByID(ctx context.Context, ccy currency.Code, tfa string, id int64) (*AccountSummaryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, transfer id has to be positive integer", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if tfa != "" {
+ params.Set("tfa", tfa)
+ }
+ params.Set("id", strconv.FormatInt(id, 10))
+ var resp *AccountSummaryData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, cancelTransferByID, params, &resp)
+}
+
+// CreateDepositAddress creates a deposit address for the currency requested
+func (d *Deribit) CreateDepositAddress(ctx context.Context, ccy currency.Code) (*DepositAddressData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp *DepositAddressData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ createDepositAddress, params, &resp)
+}
+
+// GetCurrentDepositAddress gets the current deposit address for the requested currency
+func (d *Deribit) GetCurrentDepositAddress(ctx context.Context, ccy currency.Code) (*DepositAddressData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp *DepositAddressData
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getCurrentDepositAddress, params, &resp)
+ if err != nil {
+ return nil, err
+ } else if resp == nil {
+ return nil, common.ErrNoResponse
+ }
+ return resp, nil
+}
+
+// GetDeposits gets the deposits of a given currency
+func (d *Deribit) GetDeposits(ctx context.Context, ccy currency.Code, count, offset int64) (*DepositsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if offset != 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ var resp *DepositsData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getDeposits, params, &resp)
+}
+
+// GetTransfers gets transfers data for the requested currency
+func (d *Deribit) GetTransfers(ctx context.Context, ccy currency.Code, count, offset int64) (*TransfersData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if offset != 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ var resp *TransfersData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getTransfers, params, &resp)
+}
+
+// GetWithdrawals gets withdrawals data for a requested currency
+func (d *Deribit) GetWithdrawals(ctx context.Context, ccy currency.Code, count, offset int64) (*WithdrawalsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if offset != 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ var resp *WithdrawalsData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getWithdrawals, params, &resp)
+}
+
+// SubmitTransferBetweenSubAccounts transfer funds between two (sub)accounts.
+// Id of the source (sub)account. Can be found in My Account >> Subaccounts tab. By default, it is the Id of the account which made the request.
+// However, if a different "source" is specified, the user must possess the mainaccount scope, and only other subaccounts can be designated as the source.
+func (d *Deribit) SubmitTransferBetweenSubAccounts(ctx context.Context, ccy currency.Code, amount float64, destinationID int64, source string) (*TransferData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if destinationID <= 0 {
+ return nil, errInvalidDestinationID
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ params.Set("destination", strconv.FormatInt(destinationID, 10))
+ params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
+ if source != "" {
+ params.Set("source", source)
+ }
+ var resp *TransferData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, submitTransferBetweenSubAccounts, params, &resp)
+}
+
+// SubmitTransferToSubAccount submits a request to transfer a currency to a subaccount
+func (d *Deribit) SubmitTransferToSubAccount(ctx context.Context, ccy currency.Code, amount float64, destinationID int64) (*TransferData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if destinationID <= 0 {
+ return nil, errInvalidDestinationID
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ params.Set("destination", strconv.FormatInt(destinationID, 10))
+ params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
+ var resp *TransferData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ submitTransferToSubaccount, params, &resp)
+}
+
+// SubmitTransferToUser submits a request to transfer a currency to another user
+func (d *Deribit) SubmitTransferToUser(ctx context.Context, ccy currency.Code, tfa, destinationAddress string, amount float64) (*TransferData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if destinationAddress == "" {
+ return nil, errInvalidCryptoAddress
+ }
+ params := url.Values{}
+ if tfa != "" {
+ params.Set("tfa", tfa)
+ }
+ params.Set("currency", ccy.String())
+ params.Set("destination", destinationAddress)
+ params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
+ var resp *TransferData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, submitTransferToUser, params, &resp)
+}
+
+// SubmitWithdraw submits a withdrawal request to the exchange for the requested currency
+func (d *Deribit) SubmitWithdraw(ctx context.Context, ccy currency.Code, address, priority string, amount float64) (*WithdrawData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if address == "" {
+ return nil, errInvalidCryptoAddress
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ params.Set("address", address)
+ if priority != "" {
+ params.Set("priority", priority)
+ }
+ params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
+ var resp *WithdrawData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ submitWithdraw, params, &resp)
+}
+
+// GetAnnouncements retrieves announcements. Default "start_timestamp" parameter value is current timestamp, "count" parameter value must be between 1 and 50, default is 5.
+func (d *Deribit) GetAnnouncements(ctx context.Context, startTime time.Time, count int64) ([]Announcement, error) {
+ params := url.Values{}
+ if !startTime.IsZero() {
+ params.Set("start_time", strconv.FormatInt(startTime.UnixMilli(), 10))
+ }
+ if count > 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ var resp []Announcement
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getAnnouncements, params), &resp)
+}
+
+// GetPublicPortfolioMargins public version of the method calculates portfolio margin info for simulated position. For concrete user position, the private version of the method must be used. The public version of the request has special restricted rate limit (not more than once per a second for the IP).
+func (d *Deribit) GetPublicPortfolioMargins(ctx context.Context, ccy currency.Code, simulatedPositions map[string]float64) (*PortfolioMargin, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if len(simulatedPositions) != 0 {
+ values, err := json.Marshal(simulatedPositions)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("simulated_positions", string(values))
+ }
+ var resp *PortfolioMargin
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, portfolioMarginEPL, common.EncodeURLValues(getPublicPortfolioMargins, params), &resp)
+}
+
+// ChangeAPIKeyName changes the name of the api key requested
+func (d *Deribit) ChangeAPIKeyName(ctx context.Context, id int64, name string) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ if !alphaNumericRegExp.MatchString(name) {
+ return nil, errUnacceptableAPIKey
+ }
+ params := url.Values{}
+ params.Set("id", strconv.FormatInt(id, 10))
+ params.Set("name", name)
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ changeAPIKeyName, params, &resp)
+}
+
+// ChangeMarginModel change margin model
+// Margin model: 'cross_pm', 'cross_sm', 'segregated_pm', 'segregated_sm'
+// 'dry_run': If true request returns the result without switching the margining model. Default: false
+func (d *Deribit) ChangeMarginModel(ctx context.Context, userID int64, marginModel string, dryRun bool) ([]TogglePortfolioMarginResponse, error) {
+ if marginModel == "" {
+ return nil, errInvalidMarginModel
+ }
+ params := url.Values{}
+ params.Set("margin_model", marginModel)
+ if userID != 0 {
+ params.Set("user_id", strconv.FormatInt(userID, 10))
+ }
+ if dryRun {
+ params.Set("dry_run", "true")
+ }
+ var resp []TogglePortfolioMarginResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, changeMarginModel, params, &resp)
+}
+
+// ChangeScopeInAPIKey changes the scope of the api key requested
+func (d *Deribit) ChangeScopeInAPIKey(ctx context.Context, id int64, maxScope string) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("id", strconv.FormatInt(id, 10))
+ params.Set("max_scope", maxScope)
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ changeScopeInAPIKey, params, &resp)
+}
+
+// ChangeSubAccountName changes the name of the requested subaccount id
+func (d *Deribit) ChangeSubAccountName(ctx context.Context, sid int64, name string) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ if name == "" {
+ return errInvalidUsername
+ }
+ params := url.Values{}
+ params.Set("sid", strconv.FormatInt(sid, 10))
+ params.Set("name", name)
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ changeSubAccountName, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return errSubAccountNameChangeFailed
+ }
+ return nil
+}
+
+// CreateAPIKey creates an api key based on the provided settings
+func (d *Deribit) CreateAPIKey(ctx context.Context, maxScope, name string, defaultKey bool) (*APIKeyData, error) {
+ params := url.Values{}
+ params.Set("max_scope", maxScope)
+ if name != "" {
+ params.Set("name", name)
+ }
+ if defaultKey {
+ params.Set("default", "true")
+ }
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ createAPIKey, params, &resp)
+}
+
+// CreateSubAccount creates a new subaccount
+func (d *Deribit) CreateSubAccount(ctx context.Context) (*SubAccountData, error) {
+ var resp *SubAccountData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ createSubAccount, nil, &resp)
+}
+
+// DisableAPIKey disables the api key linked to the provided id
+func (d *Deribit) DisableAPIKey(ctx context.Context, id int64) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("id", strconv.FormatInt(id, 10))
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ disableAPIKey, params, &resp)
+}
+
+// EditAPIKey edits existing API key. At least one parameter is required.
+// Describes maximal access for tokens generated with given key, possible values:
+// trade:[read, read_write, none],
+// wallet:[read, read_write, none],
+// account:[read, read_write, none],
+// block_trade:[read, read_write, none].
+func (d *Deribit) EditAPIKey(ctx context.Context, id int64, maxScope, name string, enabled bool, enabledFeatures, ipWhitelist []string) (*APIKeyData, error) {
+ if id == 0 {
+ return nil, errInvalidAPIKeyID
+ }
+ if maxScope == "" {
+ return nil, errMaxScopeIsRequired
+ }
+ params := url.Values{}
+ if name != "" {
+ params.Set("name", name)
+ }
+ if enabled {
+ params.Set("enabled", "true")
+ }
+ if len(enabledFeatures) > 0 {
+ enabledFeaturesByte, err := json.Marshal(enabledFeatures)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("enabled_features", string(enabledFeaturesByte))
+ }
+ if len(ipWhitelist) > 0 {
+ ipWhitelistByte, err := json.Marshal(ipWhitelist)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("ip_whitelist", string(ipWhitelistByte))
+ }
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, editAPIKey, params, &resp)
+}
+
+// EnableAffiliateProgram enables the affiliate program
+func (d *Deribit) EnableAffiliateProgram(ctx context.Context) error {
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ enableAffiliateProgram, nil, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return errors.New("could not enable affiliate program")
+ }
+ return nil
+}
+
+// EnableAPIKey enables the api key linked to the provided id
+func (d *Deribit) EnableAPIKey(ctx context.Context, id int64) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("id", strconv.FormatInt(id, 10))
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ enableAPIKey, params, &resp)
+}
+
+// GetAccessLog lists access logs for the user
+func (d *Deribit) GetAccessLog(ctx context.Context, offset, count int64) (*AccessLog, error) {
+ params := url.Values{}
+ if offset > 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ if count > 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ var resp *AccessLog
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getAccessLog, params, &resp)
+}
+
+// GetAffiliateProgramInfo gets the affiliate program info
+func (d *Deribit) GetAffiliateProgramInfo(ctx context.Context) (*AffiliateProgramInfo, error) {
+ var resp *AffiliateProgramInfo
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getAffiliateProgramInfo, nil, &resp)
+}
+
+// GetEmailLanguage gets the current language set for the email
+func (d *Deribit) GetEmailLanguage(ctx context.Context) (string, error) {
+ var resp string
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getEmailLanguage, nil, &resp)
+}
+
+// GetNewAnnouncements gets new announcements
+func (d *Deribit) GetNewAnnouncements(ctx context.Context) ([]Announcement, error) {
+ var resp []Announcement
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getNewAnnouncements, nil, &resp)
+}
+
+// GetPrivatePortfolioMargins calculates portfolio margin info for simulated position or current position of the user. This request has special restricted rate limit (not more than once per a second).
+func (d *Deribit) GetPrivatePortfolioMargins(ctx context.Context, ccy currency.Code, accPositions bool, simulatedPositions map[string]float64) (*PortfolioMargin, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if accPositions {
+ params.Set("acc_positions", "true")
+ }
+ if len(simulatedPositions) != 0 {
+ values, err := json.Marshal(simulatedPositions)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("simulated_positions", string(values))
+ }
+ var resp *PortfolioMargin
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getPrivatePortfolioMargins, params, &resp)
+}
+
+// GetPosition gets the data of all positions in the requested instrument name
+func (d *Deribit) GetPosition(ctx context.Context, instrument string) (*PositionData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ var resp *PositionData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getPosition, params, &resp)
+}
+
+// GetSubAccounts gets all subaccounts' data
+func (d *Deribit) GetSubAccounts(ctx context.Context, withPortfolio bool) ([]SubAccountData, error) {
+ params := url.Values{}
+ if withPortfolio {
+ params.Set("with_portfolio", "true")
+ }
+ var resp []SubAccountData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getSubAccounts, params, &resp)
+}
+
+// GetSubAccountDetails retrieves sub accounts detail information.
+func (d *Deribit) GetSubAccountDetails(ctx context.Context, ccy currency.Code, withOpenOrders bool) ([]SubAccountDetail, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if withOpenOrders {
+ params.Set("with_open_orders", "true")
+ }
+ var resp []SubAccountDetail
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getSubAccountDetails, params, &resp)
+}
+
+// GetPositions gets positions data of the user account
+func (d *Deribit) GetPositions(ctx context.Context, ccy currency.Code, kind string) ([]PositionData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ var resp []PositionData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getPositions, params, &resp)
+}
+
+// GetTransactionLog gets transaction logs' data
+func (d *Deribit) GetTransactionLog(ctx context.Context, ccy currency.Code, query string, startTime, endTime time.Time, count, continuation int64) (*TransactionsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if query != "" {
+ params.Set("query", query)
+ }
+ err := common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if continuation != 0 {
+ params.Set("continuation", strconv.FormatInt(continuation, 10))
+ }
+ var resp *TransactionsData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getTransactionLog, params, &resp)
+}
+
+// GetUserLocks retrieves information about locks on user account.
+func (d *Deribit) GetUserLocks(ctx context.Context) ([]UserLock, error) {
+ var resp []UserLock
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getUserLocks, nil, &resp)
+}
+
+// ListAPIKeys lists all the api keys associated with a user account
+func (d *Deribit) ListAPIKeys(ctx context.Context, tfa string) ([]APIKeyData, error) {
+ params := url.Values{}
+ if tfa != "" {
+ params.Set("tfa", tfa)
+ }
+ var resp []APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ listAPIKeys, params, &resp)
+}
+
+// GetCustodyAccounts retrieves user custody accounts list.
+func (d *Deribit) GetCustodyAccounts(ctx context.Context, ccy currency.Code) ([]CustodyAccount, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp []CustodyAccount
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, listCustodyAccounts, params, &resp)
+}
+
+// RemoveAPIKey removes api key vid ID
+func (d *Deribit) RemoveAPIKey(ctx context.Context, id int64) error {
+ if id <= 0 {
+ return fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("id", strconv.FormatInt(id, 10))
+ var resp interface{}
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, removeAPIKey, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return errors.New("removal of the api key requested failed")
+ }
+ return nil
+}
+
+// RemoveSubAccount removes a subaccount given its id
+func (d *Deribit) RemoveSubAccount(ctx context.Context, subAccountID int64) error {
+ params := url.Values{}
+ params.Set("subaccount_id", strconv.FormatInt(subAccountID, 10))
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, removeSubAccount, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("removal of sub account %v failed", subAccountID)
+ }
+ return nil
+}
+
+// ResetAPIKey resets the api key to its default settings
+func (d *Deribit) ResetAPIKey(ctx context.Context, id int64) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("id", strconv.FormatInt(id, 10))
+ var resp *APIKeyData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ resetAPIKey, params, &resp)
+}
+
+// SetAnnouncementAsRead sets an announcement as read
+func (d *Deribit) SetAnnouncementAsRead(ctx context.Context, id int64) error {
+ if id <= 0 {
+ return fmt.Errorf("%w, invalid announcement id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("announcement_id", strconv.FormatInt(id, 10))
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ setAnnouncementAsRead, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("setting announcement %v as read failed", id)
+ }
+ return nil
+}
+
+// SetEmailForSubAccount links an email given to the designated subaccount
+func (d *Deribit) SetEmailForSubAccount(ctx context.Context, sid int64, email string) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ if !common.MatchesEmailPattern(email) {
+ return errInvalidEmailAddress
+ }
+ params := url.Values{}
+ params.Set("sid", strconv.FormatInt(sid, 10))
+ params.Set("email", email)
+ var resp interface{}
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ setEmailForSubAccount, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("could not link email (%v) to subaccount %v", email, sid)
+ }
+ return nil
+}
+
+// SetEmailLanguage sets a requested language for an email
+func (d *Deribit) SetEmailLanguage(ctx context.Context, language string) error {
+ if language == "" {
+ return errLanguageIsRequired
+ }
+ params := url.Values{}
+ params.Set("language", language)
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, setEmailLanguage, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("could not set the email language to %v", language)
+ }
+ return nil
+}
+
+// SetSelfTradingConfig configure self trading behavior
+// mode: Self trading prevention behavior. Possible values: 'reject_taker', 'cancel_maker'
+// extended_to_subaccounts: If value is true trading is prevented between subaccounts of given account
+func (d *Deribit) SetSelfTradingConfig(ctx context.Context, mode string, extendedToSubaccounts bool) (string, error) {
+ if mode == "" {
+ return "", errTradeModeIsRequired
+ }
+ params := url.Values{}
+ params.Set("mode", mode)
+ if extendedToSubaccounts {
+ params.Set("extended_to_subaccounts", "true")
+ } else {
+ params.Set("extended_to_subaccounts", "false")
+ }
+ var resp string
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, setSelfTradingConfig, params, &resp)
+}
+
+// ToggleNotificationsFromSubAccount toggles the notifications from a subaccount specified
+func (d *Deribit) ToggleNotificationsFromSubAccount(ctx context.Context, sid int64, state bool) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("sid", strconv.FormatInt(sid, 10))
+ params.Set("state", strconv.FormatBool(state))
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ toggleNotificationsFromSubAccount, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("toggling notifications for subaccount %v to %v failed", sid, state)
+ }
+ return nil
+}
+
+// TogglePortfolioMargining toggle between SM and PM models.
+func (d *Deribit) TogglePortfolioMargining(ctx context.Context, userID int64, enabled, dryRun bool) ([]TogglePortfolioMarginResponse, error) {
+ if userID == 0 {
+ return nil, errUserIDRequired
+ }
+ params := url.Values{}
+ params.Set("user_id", strconv.FormatInt(userID, 10))
+ params.Set("enabled", strconv.FormatBool(enabled))
+ if dryRun {
+ params.Set("dry_run", "true")
+ }
+ var resp []TogglePortfolioMarginResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, togglePortfolioMargining, params, &resp)
+}
+
+// ToggleSubAccountLogin toggles access for subaccount login
+func (d *Deribit) ToggleSubAccountLogin(ctx context.Context, subAccountUserID int64, state bool) error {
+ if subAccountUserID <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("sid", strconv.FormatInt(subAccountUserID, 10))
+ params.Set("state", strconv.FormatBool(state))
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, toggleSubAccountLogin, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("toggling login access for subaccount %v to %v failed", subAccountUserID, state)
+ }
+ return nil
+}
+
+// SubmitBuy submits a private buy request through the websocket connection.
+func (d *Deribit) SubmitBuy(ctx context.Context, arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w parameter is required", common.ErrNilPointer)
+ }
+ params, err := checkInstrument(arg.Instrument)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
+ if arg.OrderType != "" {
+ params.Set("type", arg.OrderType)
+ }
+ if arg.Price != 0 {
+ params.Set("price", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
+ }
+ if arg.Label != "" {
+ params.Set("label", arg.Label)
+ }
+ if arg.TimeInForce != "" {
+ params.Set("time_in_force", arg.TimeInForce)
+ }
+ if arg.MaxShow != 0 {
+ params.Set("max_show", strconv.FormatFloat(arg.MaxShow, 'f', -1, 64))
+ }
+ if arg.PostOnly {
+ params.Set("post_only", "true")
+ }
+ if arg.RejectPostOnly {
+ params.Set("reject_post_only", "true")
+ }
+ if arg.ReduceOnly {
+ params.Set("reduce_only", "true")
+ }
+ if arg.MMP {
+ params.Set("mmp", "true")
+ }
+ if arg.TriggerPrice != 0 {
+ params.Set("trigger_price", strconv.FormatFloat(arg.TriggerPrice, 'f', -1, 64))
+ }
+ if arg.Trigger != "" {
+ params.Set("trigger", arg.Trigger)
+ }
+ if arg.Advanced != "" {
+ params.Set("advanced", arg.Advanced)
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitBuy, params, &resp)
+}
+
+// SubmitSell submits a sell request with the parameters provided
+func (d *Deribit) SubmitSell(ctx context.Context, arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w argument is required", common.ErrNilPointer)
+ }
+ params, err := checkInstrument(arg.Instrument)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
+ if arg.OrderType != "" {
+ params.Set("type", arg.OrderType)
+ }
+ if arg.Label != "" {
+ params.Set("label", arg.Label)
+ }
+ if arg.TimeInForce != "" {
+ params.Set("time_in_force", arg.TimeInForce)
+ }
+ if arg.MaxShow != 0 {
+ params.Set("max_show", strconv.FormatFloat(arg.MaxShow, 'f', -1, 64))
+ }
+ if (arg.OrderType == "limit" || arg.OrderType == "stop_limit") && arg.Price <= 0 {
+ return nil, errInvalidPrice
+ }
+ params.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
+ if arg.PostOnly {
+ params.Set("post_only", "true")
+ }
+ if arg.RejectPostOnly {
+ params.Set("reject_post_only", "true")
+ }
+ if arg.ReduceOnly {
+ params.Set("reduce_only", "true")
+ }
+ if arg.MMP {
+ params.Set("mmp", "true")
+ }
+ if arg.TriggerPrice != 0 {
+ params.Set("trigger_price", strconv.FormatFloat(arg.TriggerPrice, 'f', -1, 64))
+ }
+ if arg.Trigger != "" {
+ params.Set("trigger", arg.Trigger)
+ }
+ if arg.Advanced != "" {
+ params.Set("advanced", arg.Advanced)
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestSpot, matchingEPL, http.MethodGet, submitSell, params, &resp)
+}
+
+// SubmitEdit submits an edit order request
+func (d *Deribit) SubmitEdit(ctx context.Context, arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w parameter is required", common.ErrNilPointer)
+ }
+ if arg.OrderID == "" {
+ return nil, fmt.Errorf("%w, order id is required", errInvalidID)
+ }
+ if arg.Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ params := url.Values{}
+ params.Set("order_id", arg.OrderID)
+ params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
+ if arg.PostOnly {
+ params.Set("post_only", "true")
+ }
+ if arg.RejectPostOnly {
+ params.Set("reject_post_only", "true")
+ }
+ if arg.ReduceOnly {
+ params.Set("reduce_only", "true")
+ }
+ if arg.MMP {
+ params.Set("mmp", "true")
+ }
+ if arg.TriggerPrice != 0 {
+ params.Set("trigger_price", strconv.FormatFloat(arg.TriggerPrice, 'f', -1, 64))
+ }
+ if arg.Advanced != "" {
+ params.Set("advanced", arg.Advanced)
+ }
+ if arg.Price > 0 {
+ params.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64))
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet, submitEdit, params, &resp)
+}
+
+// EditOrderByLabel submits an edit order request sorted via label
+func (d *Deribit) EditOrderByLabel(ctx context.Context, arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w parameter is required", common.ErrNilPointer)
+ }
+ params, err := checkInstrument(arg.Instrument)
+ if err != nil {
+ return nil, err
+ }
+ if arg.Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if arg.Label != "" {
+ params.Set("label", arg.Label)
+ }
+ params.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64))
+ if arg.PostOnly {
+ params.Set("post_only", "true")
+ }
+ if arg.RejectPostOnly {
+ params.Set("reject_post_only", "true")
+ }
+ if arg.ReduceOnly {
+ params.Set("reduce_only", "true")
+ }
+ if arg.MMP {
+ params.Set("mmp", "true")
+ }
+ if arg.TriggerPrice != 0 {
+ params.Set("trigger_price", strconv.FormatFloat(arg.TriggerPrice, 'f', -1, 64))
+ }
+ if arg.Advanced != "" {
+ params.Set("advanced", arg.Advanced)
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ editByLabel, params, &resp)
+}
+
+// SubmitCancel sends a request to cancel the order via its orderID
+func (d *Deribit) SubmitCancel(ctx context.Context, orderID string) (*PrivateCancelData, error) {
+ if orderID == "" {
+ return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("order_id", orderID)
+ var resp *PrivateCancelData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitCancel, params, &resp)
+}
+
+// SubmitCancelAll sends a request to cancel all user orders in all currencies and instruments
+func (d *Deribit) SubmitCancelAll(ctx context.Context, detailed bool) (*MultipleCancelResponse, error) {
+ params := url.Values{}
+ if detailed {
+ params.Set("detailed", "true")
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitCancelAll, params, &resp)
+}
+
+// SubmitCancelAllByCurrency sends a request to cancel all user orders for the specified currency
+// returns the total number of successfully cancelled orders
+func (d *Deribit) SubmitCancelAllByCurrency(ctx context.Context, ccy currency.Code, kind, orderType string, detailed bool) (*MultipleCancelResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if orderType != "" {
+ params.Set("type", orderType)
+ }
+ if detailed {
+ params.Set("detailed", "true")
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitCancelAllByCurrency, params, &resp)
+}
+
+// SubmitCancelAllByKind cancels all orders in currency(currencies), optionally filtered by instrument kind and/or order type.
+// 'kind' instrument kind . Possible values: 'future', 'option', 'spot', 'future_combo', 'option_combo', 'combo', 'any'
+// returns the total number of successfully cancelled orders
+func (d *Deribit) SubmitCancelAllByKind(ctx context.Context, ccy currency.Code, kind, orderType string, detailed bool) (*MultipleCancelResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if orderType != "" {
+ params.Set("type", orderType)
+ }
+ if detailed {
+ params.Set("detailed", "true")
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestSpot, matchingEPL, http.MethodGet, submitCancelAllByKind, params, &resp)
+}
+
+// SubmitCancelAllByInstrument sends a request to cancel all user orders for the specified instrument
+// returns the total number of successfully cancelled orders
+func (d *Deribit) SubmitCancelAllByInstrument(ctx context.Context, instrument, orderType string, detailed, includeCombos bool) (*MultipleCancelResponse, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if orderType != "" {
+ params.Set("type", orderType)
+ }
+ if detailed {
+ params.Set("detailed", "true")
+ }
+ if includeCombos {
+ params.Set("include_combos", "true")
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitCancelAllByInstrument, params, &resp)
+}
+
+// SubmitCancelByLabel sends a request to cancel all user orders for the specified label
+// returns the total number of successfully cancelled orders
+func (d *Deribit) SubmitCancelByLabel(ctx context.Context, label string, ccy currency.Code, detailed bool) (*MultipleCancelResponse, error) {
+ params := url.Values{}
+ params.Set("label", label)
+ if !ccy.IsEmpty() {
+ params.Set("currency", ccy.String())
+ }
+ if detailed {
+ params.Set("detailed", "true")
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitCancelByLabel, params, &resp)
+}
+
+// SubmitCancelQuotes cancels quotes based on the provided type.
+// Delta cancels quotes within a Delta range defined by MinDelta and MaxDelta.
+// quote_set_id cancels quotes by a specific Quote Set identifier.
+// instrument cancels all quotes associated with a particular instrument. kind cancels all quotes for a certain kind.
+// currency cancels all quotes in a specified currency. "all" cancels all quotes.
+//
+// possible cancel_type values are delta, 'quote_set_id', 'instrument', 'instrument_kind', 'currency', and 'all'
+// possible kind values are future 'option', 'spot', 'future_combo', 'option_combo', 'combo', and 'any'
+func (d *Deribit) SubmitCancelQuotes(ctx context.Context, ccy currency.Code, minDelta, maxDelta float64, cancelType, quoteSetID, instrumentName, kind string, detailed bool) (*MultipleCancelResponse, error) {
+ if cancelType == "" {
+ return nil, errors.New("cancel type is required")
+ }
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("cancel_type", cancelType)
+ params.Set("currency", ccy.String())
+ if detailed {
+ params.Set("detailed", "true")
+ }
+ if minDelta > 0 {
+ params.Set("min_delta", strconv.FormatFloat(minDelta, 'f', -1, 64))
+ }
+ if maxDelta > 0 {
+ params.Set("max_delta", strconv.FormatFloat(maxDelta, 'f', -1, 64))
+ }
+ if quoteSetID != "" {
+ params.Set("quote_set_id", quoteSetID)
+ }
+ if instrumentName != "" {
+ params.Set("instrument_name", instrumentName)
+ }
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet, submitCancelQuotes, params, &resp)
+}
+
+// SubmitClosePosition sends a request to cancel all user orders for the specified label
+func (d *Deribit) SubmitClosePosition(ctx context.Context, instrument, orderType string, price float64) (*PrivateTradeData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if orderType != "" {
+ params.Set("type", orderType)
+ }
+ params.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
+ var resp *PrivateTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet,
+ submitClosePosition, params, &resp)
+}
+
+// GetMargins sends a request to fetch account margins data
+func (d *Deribit) GetMargins(ctx context.Context, instrument string, amount, price float64) (*MarginsData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if price <= 0 {
+ return nil, errInvalidPrice
+ }
+ params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
+ params.Set("price", strconv.FormatFloat(price, 'f', -1, 64))
+ var resp *MarginsData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getMargins, params, &resp)
+}
+
+// GetMMPConfig sends a request to fetch the config for MMP of the requested currency
+func (d *Deribit) GetMMPConfig(ctx context.Context, ccy currency.Code) (*MMPConfigData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp *MMPConfigData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getMMPConfig, params, &resp)
+}
+
+// GetOpenOrdersByCurrency retrieves open orders data sorted by requested params
+func (d *Deribit) GetOpenOrdersByCurrency(ctx context.Context, ccy currency.Code, kind, orderType string) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if orderType != "" {
+ params.Set("type", orderType)
+ }
+ var resp []OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getOpenOrdersByCurrency, params, &resp)
+}
+
+// GetOpenOrdersByLabel retrieves open orders using label and currency
+func (d *Deribit) GetOpenOrdersByLabel(ctx context.Context, ccy currency.Code, label string) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if label != "" {
+ params.Set("label", label)
+ }
+ var resp []OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getOpenOrdersByLabel, params, &resp)
+}
+
+// GetOpenOrdersByInstrument sends a request to fetch open orders data sorted by requested params
+func (d *Deribit) GetOpenOrdersByInstrument(ctx context.Context, instrument, orderType string) ([]OrderData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if orderType != "" {
+ params.Set("type", orderType)
+ }
+ var resp []OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getOpenOrdersByInstrument, params, &resp)
+}
+
+// GetOrderHistoryByCurrency sends a request to fetch order history according to given params and currency
+func (d *Deribit) GetOrderHistoryByCurrency(ctx context.Context, ccy currency.Code, kind string, count, offset int64, includeOld, includeUnfilled bool) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if offset != 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ if includeOld {
+ params.Set("include_old", "true")
+ }
+ if includeUnfilled {
+ params.Set("include_unfilled", "true")
+ }
+ var resp []OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getOrderHistoryByCurrency, params, &resp)
+}
+
+// GetOrderHistoryByInstrument sends a request to fetch order history according to given params and instrument
+func (d *Deribit) GetOrderHistoryByInstrument(ctx context.Context, instrument string, count, offset int64, includeOld, includeUnfilled bool) ([]OrderData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if offset != 0 {
+ params.Set("offset", strconv.FormatInt(offset, 10))
+ }
+ if includeOld {
+ params.Set("include_old", "true")
+ }
+ if includeUnfilled {
+ params.Set("include_unfilled", "true")
+ }
+ var resp []OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestSpot, nonMatchingEPL, http.MethodGet,
+ getOrderHistoryByInstrument, params, &resp)
+}
+
+// GetOrderMarginsByID sends a request to fetch order margins data according to their ids
+func (d *Deribit) GetOrderMarginsByID(ctx context.Context, ids []string) ([]InitialMarginInfo, error) {
+ if len(ids) == 0 {
+ return nil, fmt.Errorf("%w, order ids cannot be empty", errInvalidID)
+ }
+ params := url.Values{}
+ for a := range ids {
+ params.Add("ids[]", ids[a])
+ }
+ var resp []InitialMarginInfo
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestSpot, nonMatchingEPL, http.MethodGet,
+ getOrderMarginByIDs, params, &resp)
+}
+
+// GetOrderState sends a request to fetch order state of the order id provided
+func (d *Deribit) GetOrderState(ctx context.Context, orderID string) (*OrderData, error) {
+ if orderID == "" {
+ return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("order_id", orderID)
+ var resp *OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getOrderState, params, &resp)
+}
+
+// GetOrderStateByLabel retrieves an order state by label and currency
+func (d *Deribit) GetOrderStateByLabel(ctx context.Context, ccy currency.Code, label string) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if label != "" {
+ params.Set("label", label)
+ }
+ var resp []OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getOrderStateByLabel, params, &resp)
+}
+
+// GetTriggerOrderHistory sends a request to fetch order state of the order id provided
+func (d *Deribit) GetTriggerOrderHistory(ctx context.Context, ccy currency.Code, instrumentName, continuation string, count int64) (*OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if instrumentName != "" {
+ params.Set("instrument_name", instrumentName)
+ }
+ if continuation != "" {
+ params.Set("continuation", continuation)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ var resp *OrderData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getTriggerOrderHistory, params, &resp)
+}
+
+// GetUserTradesByCurrency sends a request to fetch user trades sorted by currency
+func (d *Deribit) GetUserTradesByCurrency(ctx context.Context, ccy currency.Code, kind, startID, endID, sorting string, count int64, includeOld bool) (*UserTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if startID != "" {
+ params.Set("start_id", startID)
+ }
+ if endID != "" {
+ params.Set("end_id", endID)
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if includeOld {
+ params.Set("include_old", "true")
+ }
+ var resp *UserTradesData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getUserTradesByCurrency, params, &resp)
+}
+
+// GetUserTradesByCurrencyAndTime sends a request to fetch user trades sorted by currency and time
+func (d *Deribit) GetUserTradesByCurrencyAndTime(ctx context.Context, ccy currency.Code, kind, sorting string, count int64, startTime, endTime time.Time) (*UserTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if kind != "" {
+ params.Set("kind", kind)
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if !startTime.IsZero() {
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ }
+ if !endTime.IsZero() {
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ }
+ var resp *UserTradesData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getUserTradesByCurrencyAndTime, params, &resp)
+}
+
+// GetUserTradesByInstrument sends a request to fetch user trades sorted by instrument
+func (d *Deribit) GetUserTradesByInstrument(ctx context.Context, instrument, sorting string, startSeq, endSeq, count int64, includeOld bool) (*UserTradesData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if startSeq != 0 {
+ params.Set("start_seq", strconv.FormatInt(startSeq, 10))
+ }
+ if endSeq != 0 {
+ params.Set("end_seq", strconv.FormatInt(endSeq, 10))
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if includeOld {
+ params.Set("include_old", "true")
+ }
+ var resp *UserTradesData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getUserTradesByInstrument, params, &resp)
+}
+
+// GetUserTradesByInstrumentAndTime sends a request to fetch user trades sorted by instrument and time
+func (d *Deribit) GetUserTradesByInstrumentAndTime(ctx context.Context, instrument, sorting string, count int64, startTime, endTime time.Time) (*UserTradesData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ err = common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ params.Set("start_timestamp", strconv.FormatInt(startTime.UnixMilli(), 10))
+ params.Set("end_timestamp", strconv.FormatInt(endTime.UnixMilli(), 10))
+ var resp *UserTradesData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getUserTradesByInstrumentAndTime, params, &resp)
+}
+
+// GetUserTradesByOrder sends a request to get user trades fetched by orderID
+func (d *Deribit) GetUserTradesByOrder(ctx context.Context, orderID, sorting string) (*UserTradesData, error) {
+ if orderID == "" {
+ return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
+ }
+ params := url.Values{}
+ params.Set("order_id", orderID)
+ if sorting != "" {
+ params.Set("sorting", sorting)
+ }
+ var resp *UserTradesData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getUserTradesByOrder, params, &resp)
+}
+
+// ResetMMP sends a request to reset MMP for a currency provided
+func (d *Deribit) ResetMMP(ctx context.Context, ccy currency.Code) error {
+ if ccy.IsEmpty() {
+ return currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, resetMMP, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("mmp could not be reset for %v", ccy.String())
+ }
+ return nil
+}
+
+// SendRequestForQuote sends RFQ on a given instrument.
+func (d *Deribit) SendRequestForQuote(ctx context.Context, instrumentName string, amount float64, side order.Side) error {
+ params, err := checkInstrument(instrumentName)
+ if err != nil {
+ return err
+ }
+ if amount > 0 {
+ params.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
+ }
+ if side != order.UnknownSide {
+ params.Set("side", side.String())
+ }
+ var resp string
+ err = d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, sendRFQ, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("rfq couldn't send for %v", instrumentName)
+ }
+ return nil
+}
+
+// SetMMPConfig sends a request to set the given parameter values to the mmp config for the provided currency
+func (d *Deribit) SetMMPConfig(ctx context.Context, ccy currency.Code, interval kline.Interval, frozenTime int64, quantityLimit, deltaLimit float64) error {
+ if ccy.IsEmpty() {
+ return currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ intervalString, err := d.GetResolutionFromInterval(interval)
+ if err != nil {
+ return err
+ }
+ params.Set("interval", intervalString)
+ params.Set("frozen_time", strconv.FormatInt(frozenTime, 10))
+ if quantityLimit != 0 {
+ params.Set("quantity_limit", strconv.FormatFloat(quantityLimit, 'f', -1, 64))
+ }
+ if deltaLimit != 0 {
+ params.Set("delta_limit", strconv.FormatFloat(deltaLimit, 'f', -1, 64))
+ }
+ var resp string
+ err = d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ setMMPConfig, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("mmp data could not be set for %v", ccy.String())
+ }
+ return nil
+}
+
+// GetSettlementHistoryByInstrument sends a request to fetch settlement history data sorted by instrument
+func (d *Deribit) GetSettlementHistoryByInstrument(ctx context.Context, instrument, settlementType, continuation string, count int64, searchStartTimeStamp time.Time) (*PrivateSettlementsHistoryData, error) {
+ params, err := checkInstrument(instrument)
+ if err != nil {
+ return nil, err
+ }
+ if settlementType != "" {
+ params.Set("settlement_type", settlementType)
+ }
+ if continuation != "" {
+ params.Set("continuation", continuation)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if !searchStartTimeStamp.IsZero() {
+ params.Set("search_start_timestamp", strconv.FormatInt(searchStartTimeStamp.UnixMilli(), 10))
+ }
+ var resp *PrivateSettlementsHistoryData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getSettlementHistoryByInstrument, params, &resp)
+}
+
+// GetSettlementHistoryByCurency sends a request to fetch settlement history data sorted by currency
+func (d *Deribit) GetSettlementHistoryByCurency(ctx context.Context, ccy currency.Code, settlementType, continuation string, count int64, searchStartTimeStamp time.Time) (*PrivateSettlementsHistoryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if settlementType != "" {
+ params.Set("settlement_type", settlementType)
+ }
+ if continuation != "" {
+ params.Set("continuation", continuation)
+ }
+ if count != 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ if !searchStartTimeStamp.IsZero() {
+ params.Set("search_start_timestamp", strconv.FormatInt(searchStartTimeStamp.UnixMilli(), 10))
+ }
+ var resp *PrivateSettlementsHistoryData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet,
+ getSettlementHistoryByCurrency, params, &resp)
+}
+
+// SendHTTPAuthRequest sends an authenticated request to deribit api
+func (d *Deribit) SendHTTPAuthRequest(ctx context.Context, ep exchange.URL, epl request.EndpointLimit, method, path string, params url.Values, result interface{}) error {
+ endpoint, err := d.API.Endpoints.GetURL(ep)
+ if err != nil {
+ return err
+ }
+ reqDataStr := method + "\n" + deribitAPIVersion + "/" + common.EncodeURLValues(path, params) + "\n" + "\n"
+ n := d.Requester.GetNonce(nonce.UnixNano).String()
+ strTS := strconv.FormatInt(time.Now().UnixMilli(), 10)
+ str2Sign := strTS + "\n" + n + "\n" + reqDataStr
+ creds, err := d.GetCredentials(ctx)
+ if err != nil {
+ return fmt.Errorf("%w, %v", request.ErrAuthRequestFailed, err)
+ }
+ hmac, err := crypto.GetHMAC(crypto.HashSHA256,
+ []byte(str2Sign),
+ []byte(creds.Secret))
+ if err != nil {
+ return err
+ }
+ headers := make(map[string]string)
+ headerString := "deri-hmac-sha256 id=" + creds.Key + ",ts=" + strTS + ",sig=" + crypto.HexEncodeToString(hmac) + ",nonce=" + n
+ headers["Authorization"] = headerString
+ headers["Content-Type"] = "application/json"
+ var tempData struct {
+ JSONRPC string `json:"jsonrpc"`
+ ID int64 `json:"id"`
+ Data json.RawMessage `json:"result"`
+ Error ErrInfo `json:"error"`
+ }
+ err = d.SendPayload(context.Background(), epl, func() (*request.Item, error) {
+ return &request.Item{
+ Method: method,
+ Path: endpoint + deribitAPIVersion + "/" + common.EncodeURLValues(path, params),
+ Headers: headers,
+ Result: &tempData,
+ Verbose: d.Verbose,
+ HTTPDebugging: d.HTTPDebugging,
+ HTTPRecording: d.HTTPRecording,
+ }, nil
+ }, request.AuthenticatedRequest)
+ if err != nil {
+ return fmt.Errorf("%w %v", request.ErrAuthRequestFailed, err)
+ }
+ if tempData.Error.Code != 0 {
+ var errParamInfo string
+ if tempData.Error.Data.Param != "" {
+ errParamInfo = fmt.Sprintf(" param: %s reason: %s", tempData.Error.Data.Param, tempData.Error.Data.Reason)
+ }
+ return fmt.Errorf("%w code: %d msg: %s%s", request.ErrAuthRequestFailed, tempData.Error.Code, tempData.Error.Message, errParamInfo)
+ }
+ return json.Unmarshal(tempData.Data, result)
+}
+
+// Combo Books endpoints'
+
+// GetComboIDs Retrieves available combos.
+// This method can be used to get the list of all combos, or only the list of combos in the given state.
+func (d *Deribit) GetComboIDs(ctx context.Context, ccy currency.Code, state string) ([]string, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if state != "" {
+ params.Set("state", state)
+ }
+ var resp []string
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getComboIDs, params), &resp)
+}
+
+// GetComboDetails retrieves information about a combo
+func (d *Deribit) GetComboDetails(ctx context.Context, comboID string) (*ComboDetail, error) {
+ if comboID == "" {
+ return nil, errInvalidComboID
+ }
+ params := url.Values{}
+ params.Set("combo_id", comboID)
+ var resp *ComboDetail
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getComboDetails, params), &resp)
+}
+
+// GetCombos retrieves information about active combos
+func (d *Deribit) GetCombos(ctx context.Context, ccy currency.Code) ([]ComboDetail, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ var resp []ComboDetail
+ return resp, d.SendHTTPRequest(ctx, exchange.RestFutures, nonMatchingEPL, common.EncodeURLValues(getCombos, params), &resp)
+}
+
+// CreateCombo verifies and creates a combo book or returns an existing combo matching given trades
+func (d *Deribit) CreateCombo(ctx context.Context, args []ComboParam) (*ComboDetail, error) {
+ if len(args) == 0 {
+ return nil, errNoArgumentPassed
+ }
+ instrument := args[0].InstrumentName
+ for x := range args {
+ if args[x].InstrumentName == "" {
+ return nil, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ } else if instrument != args[x].InstrumentName {
+ return nil, errDifferentInstruments
+ }
+ args[x].Direction = strings.ToLower(args[x].Direction)
+ if args[x].Direction != sideBUY && args[x].Direction != sideSELL {
+ return nil, errInvalidOrderSideOrDirection
+ }
+ if args[x].Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ }
+ argsByte, err := json.Marshal(args)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ params.Set("trades", string(argsByte))
+ var resp *ComboDetail
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, createCombos, params, &resp)
+}
+
+// ExecuteBlockTrade executes a block trade request
+// The whole request have to be exact the same as in private/verify_block_trade, only role field should be set appropriately - it basically means that both sides have to agree on the same timestamp, nonce, trades fields and server will assure that role field is different between sides (each party accepted own role).
+// Using the same timestamp and nonce by both sides in private/verify_block_trade assures that even if unintentionally both sides execute given block trade with valid counterparty_signature, the given block trade will be executed only once
+func (d *Deribit) ExecuteBlockTrade(ctx context.Context, timestampMS time.Time, nonce, role string, ccy currency.Code, trades []BlockTradeParam) ([]BlockTradeResponse, error) {
+ if nonce == "" {
+ return nil, errMissingNonce
+ }
+ if role != roleMaker && role != roleTaker {
+ return nil, errInvalidTradeRole
+ }
+ if len(trades) == 0 {
+ return nil, errNoArgumentPassed
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return nil, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ trades[x].Direction = strings.ToLower(trades[x].Direction)
+ if trades[x].Direction != sideBUY && trades[x].Direction != sideSELL {
+ return nil, errInvalidOrderSideOrDirection
+ }
+ if trades[x].Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return nil, fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ signature, err := d.VerifyBlockTrade(ctx, timestampMS, nonce, role, ccy, trades)
+ if err != nil {
+ return nil, err
+ }
+ values, err := json.Marshal(trades)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if !ccy.IsEmpty() {
+ params.Set("currency", ccy.String())
+ }
+ params.Set("trades", string(values))
+ params.Set("nonce", nonce)
+ params.Set("role", role)
+ params.Set("counterparty_signature", signature)
+ params.Set("timestamp", strconv.FormatInt(timestampMS.UnixMilli(), 10))
+ var resp []BlockTradeResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet, executeBlockTrades, params, &resp)
+}
+
+// VerifyBlockTrade verifies and creates block trade signature
+func (d *Deribit) VerifyBlockTrade(ctx context.Context, timestampMS time.Time, nonce, role string, ccy currency.Code, trades []BlockTradeParam) (string, error) {
+ if nonce == "" {
+ return "", errMissingNonce
+ }
+ if role != roleMaker && role != roleTaker {
+ return "", errInvalidTradeRole
+ }
+ if len(trades) == 0 {
+ return "", errNoArgumentPassed
+ }
+ if timestampMS.IsZero() {
+ return "", errZeroTimestamp
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return "", fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ trades[x].Direction = strings.ToLower(trades[x].Direction)
+ if trades[x].Direction != sideBUY && trades[x].Direction != sideSELL {
+ return "", errInvalidOrderSideOrDirection
+ }
+ if trades[x].Amount <= 0 {
+ return "", errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return "", fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ values, err := json.Marshal(trades)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ params.Set("timestamp", strconv.FormatInt(timestampMS.UnixMilli(), 10))
+ if !ccy.IsEmpty() {
+ params.Set("currency", ccy.String())
+ }
+ params.Set("nonce", nonce)
+ params.Set("role", role)
+ params.Set("trades", string(values))
+ resp := &struct {
+ Signature string `json:"signature"`
+ }{}
+ return resp.Signature, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet, verifyBlockTrades, params, resp)
+}
+
+// InvalidateBlockTradeSignature user at any time (before the private/execute_block_trade is called) can invalidate its own signature effectively cancelling block trade
+func (d *Deribit) InvalidateBlockTradeSignature(ctx context.Context, signature string) error {
+ if signature == "" {
+ return errMissingSignature
+ }
+ params := url.Values{}
+ params.Set("signature", signature)
+ var resp string
+ err := d.SendHTTPAuthRequest(ctx, exchange.RestSpot, nonMatchingEPL, http.MethodGet, invalidateBlockTradesSignature, params, &resp)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("server response: %s", resp)
+ }
+ return nil
+}
+
+// GetUserBlockTrade returns information about users block trade
+func (d *Deribit) GetUserBlockTrade(ctx context.Context, id string) ([]BlockTradeData, error) {
+ if id == "" {
+ return nil, errMissingBlockTradeID
+ }
+ params := url.Values{}
+ params.Set("id", id)
+ var resp []BlockTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getBlockTrades, params, &resp)
+}
+
+// GetTime retrieves the current time (in milliseconds). This API endpoint can be used to check the clock skew between your software and Deribit's systems.
+func (d *Deribit) GetTime(ctx context.Context) (time.Time, error) {
+ var result int64
+ err := d.SendHTTPRequest(ctx, exchange.RestSpot, nonMatchingEPL, "public/get_time", &result)
+ if err != nil {
+ return time.Time{}, err
+ }
+ return time.UnixMilli(result), nil
+}
+
+// GetLastBlockTradesByCurrency returns list of last users block trades
+func (d *Deribit) GetLastBlockTradesByCurrency(ctx context.Context, ccy currency.Code, startID, endID string, count int64) ([]BlockTradeData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ if startID != "" {
+ params.Set("start_id", startID)
+ }
+ if endID != "" {
+ params.Set("end_id", endID)
+ }
+ if count > 0 {
+ params.Set("count", strconv.FormatInt(count, 10))
+ }
+ var resp []BlockTradeData
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, getLastBlockTradesByCurrency, params, &resp)
+}
+
+// MovePositions moves positions from source subaccount to target subaccount
+func (d *Deribit) MovePositions(ctx context.Context, ccy currency.Code, sourceSubAccountUID, targetSubAccountUID int64, trades []BlockTradeParam) ([]BlockTradeMoveResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if sourceSubAccountUID == 0 {
+ return nil, fmt.Errorf("%w source sub-account id", errMissingSubAccountID)
+ }
+ if targetSubAccountUID == 0 {
+ return nil, fmt.Errorf("%w target sub-account id", errMissingSubAccountID)
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return nil, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ if trades[x].Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return nil, fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ values, err := json.Marshal(trades)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ params.Set("currency", ccy.String())
+ params.Set("source_uid", strconv.FormatInt(sourceSubAccountUID, 10))
+ params.Set("target_uid", strconv.FormatInt(targetSubAccountUID, 10))
+ params.Set("trades", string(values))
+ var resp []BlockTradeMoveResponse
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, nonMatchingEPL, http.MethodGet, movePositions, params, &resp)
+}
+
+// SimulateBlockTrade checks if a block trade can be executed
+func (d *Deribit) SimulateBlockTrade(ctx context.Context, role string, trades []BlockTradeParam) (bool, error) {
+ if role != roleMaker && role != roleTaker {
+ return false, errInvalidTradeRole
+ }
+ if len(trades) == 0 {
+ return false, errNoArgumentPassed
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return false, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ trades[x].Direction = strings.ToLower(trades[x].Direction)
+ if trades[x].Direction != sideBUY && trades[x].Direction != sideSELL {
+ return false, errInvalidOrderSideOrDirection
+ }
+ if trades[x].Amount <= 0 {
+ return false, errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return false, fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ values, err := json.Marshal(trades)
+ if err != nil {
+ return false, err
+ }
+ params := url.Values{}
+ params.Set("role", role)
+ params.Set("trades", string(values))
+ var resp bool
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestFutures, matchingEPL, http.MethodGet, simulateBlockPosition, params, &resp)
+}
+
+// GetLockedStatus retrieves information about locked currencies
+func (d *Deribit) GetLockedStatus(ctx context.Context) (*LockedCurrenciesStatus, error) {
+ var resp *LockedCurrenciesStatus
+ return resp, d.SendHTTPRequest(ctx, exchange.RestSpot, nonMatchingEPL, "public/status", &resp)
+}
+
+// EnableCancelOnDisconnect enable Cancel On Disconnect for the connection.
+// After enabling Cancel On Disconnect all orders created by the connection will be removed when the connection is closed.
+func (d *Deribit) EnableCancelOnDisconnect(ctx context.Context, scope string) (string, error) {
+ params := url.Values{}
+ if scope != "" {
+ params.Set("scope", scope)
+ }
+ var resp string
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestSpot, nonMatchingEPL, http.MethodGet, "private/enable_cancel_on_disconnect", params, &resp)
+}
+
+// ExchangeToken generates a token for a new subject id. This method can be used to switch between subaccounts.
+func (d *Deribit) ExchangeToken(ctx context.Context, refreshToken string, subjectID int64) (*RefreshTokenInfo, error) {
+ if refreshToken == "" {
+ return nil, errRefreshTokenRequired
+ }
+ if subjectID == 0 {
+ return nil, errSubjectIDRequired
+ }
+ params := url.Values{}
+ params.Set("refresh_token", refreshToken)
+ params.Set("subject_id", strconv.FormatInt(subjectID, 10))
+ var resp *RefreshTokenInfo
+ return resp, d.SendHTTPAuthRequest(ctx, exchange.RestSpot, nonMatchingEPL, http.MethodGet, "public/exchange_token", params, &resp)
+}
+
+// ForkToken generates a token for a new named session. This method can be used only with session scoped tokens.
+func (d *Deribit) ForkToken(ctx context.Context, refreshToken, sessionName string) (*RefreshTokenInfo, error) {
+ if refreshToken == "" {
+ return nil, errRefreshTokenRequired
+ }
+ if sessionName == "" {
+ return nil, errSessionNameRequired
+ }
+ params := url.Values{}
+ params.Set("refresh_token", refreshToken)
+ params.Set("session_name", sessionName)
+ var resp *RefreshTokenInfo
+ return resp, d.SendHTTPRequest(ctx, exchange.RestSpot, nonMatchingEPL, common.EncodeURLValues("public/fork_token", params), &resp)
+}
+
+// GetAssetKind returns the asset type (kind) string representation.
+func (d *Deribit) GetAssetKind(assetType asset.Item) string {
+ switch assetType {
+ case asset.Options:
+ return "option"
+ case asset.Futures:
+ return "future"
+ case asset.FutureCombo, asset.OptionCombo, asset.Spot:
+ return assetType.String()
+ default:
+ return "any"
+ }
+}
+
+// StringToAssetKind returns the asset type (kind) from a string representation.
+func (d *Deribit) StringToAssetKind(assetType string) (asset.Item, error) {
+ assetType = strings.ToLower(assetType)
+ switch assetType {
+ case "option":
+ return asset.Options, nil
+ case "future":
+ return asset.Futures, nil
+ case "future_combo":
+ return asset.FutureCombo, nil
+ case "option_combo":
+ return asset.OptionCombo, nil
+ default:
+ return asset.Empty, nil
+ }
+}
+
+func guessAssetTypeFromInstrument(currencyPair currency.Pair) (asset.Item, error) {
+ currencyPairString := currencyPair.String()
+ vals := strings.Split(currencyPairString, currency.DashDelimiter)
+ if strings.HasSuffix(currencyPairString, "PERPETUAL") || len(vals) == 2 {
+ return asset.Futures, nil
+ } else if len(vals) == 1 {
+ if vals = strings.Split(vals[0], currency.UnderscoreDelimiter); len(vals) == 2 {
+ return asset.Spot, nil
+ }
+ }
+ added := false
+ if len(vals) >= 3 {
+ for a := range vals {
+ lastVals := strings.Split(vals[a], currency.UnderscoreDelimiter)
+ if len(lastVals) > 1 {
+ added = true
+ if a < len(vals)-1 {
+ lastVals = append(lastVals, vals[a+1:]...)
+ }
+ vals = append(vals[:a], lastVals...)
+ }
+ }
+ }
+ length := len(vals)
+ if strings.EqualFold(vals[length-1], "C") || strings.EqualFold(vals[length-1], "P") {
+ return asset.Options, nil
+ }
+ if length == 4 {
+ if added {
+ return asset.FutureCombo, nil
+ }
+ return asset.OptionCombo, nil
+ } else if length >= 5 {
+ return asset.OptionCombo, nil
+ }
+ return asset.Empty, fmt.Errorf("%w currency pair: %v", errUnsupportedInstrumentFormat, currencyPair)
+}
+
+func calculateTradingFee(feeBuilder *exchange.FeeBuilder) (float64, error) {
+ assetType, err := guessAssetTypeFromInstrument(feeBuilder.Pair)
+ if err != nil {
+ return 0, err
+ }
+ switch assetType {
+ case asset.Futures, asset.FutureCombo:
+ switch {
+ case strings.HasSuffix(feeBuilder.Pair.String(), "USDC-PERPETUAL"):
+ if feeBuilder.IsMaker {
+ return 0, nil
+ }
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0005, nil
+ case strings.HasPrefix(feeBuilder.Pair.String(), currencyBTC),
+ strings.HasPrefix(feeBuilder.Pair.String(), currencyETH):
+ if strings.HasSuffix(feeBuilder.Pair.String(), "PERPETUAL") {
+ if feeBuilder.IsMaker {
+ return 0, nil
+ }
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0005, nil
+ }
+ // weekly futures contracts
+ if feeBuilder.IsMaker {
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0001, nil
+ }
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0005, nil
+ case strings.HasPrefix(feeBuilder.Pair.String(), "SOL"): // perpetual and weekly SOL contracts
+ if feeBuilder.IsMaker {
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.00002, nil
+ }
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0005, nil
+ }
+ case asset.Options, asset.OptionCombo:
+ switch {
+ case strings.HasPrefix(feeBuilder.Pair.String(), currencyBTC),
+ strings.HasPrefix(feeBuilder.Pair.String(), currencyETH):
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0003, nil
+ case strings.HasPrefix(feeBuilder.Pair.String(), "SOL"):
+ if feeBuilder.IsMaker {
+ return 0, nil
+ }
+ return feeBuilder.Amount * feeBuilder.PurchasePrice * 0.0003, nil
+ }
+ }
+ return 0, fmt.Errorf("%w asset: %s", asset.ErrNotSupported, assetType.String())
+}
+
+// getOfflineTradeFee calculates the worst case-scenario trading fee
+func getOfflineTradeFee(price, amount float64) float64 {
+ return 0.0003 * price * amount
+}
+
+func (d *Deribit) formatFuturesTradablePair(pair currency.Pair) string {
+ var instrumentID string
+ if result := strings.Split(pair.String(), currency.DashDelimiter); len(result) == 3 {
+ instrumentID = strings.Join(result[:2], currency.UnderscoreDelimiter) + currency.DashDelimiter + result[2]
+ } else {
+ instrumentID = pair.String()
+ }
+ return instrumentID
+}
+
+// optionPairToString to format and return an Options currency pairs with the following format: MATIC_USDC-6APR24-0d98-P
+// it has both uppercase or lowercase characters, which we can not achieve with the Upper=true or Upper=false
+func (d *Deribit) optionPairToString(pair currency.Pair) string {
+ subCodes := strings.Split(pair.Quote.String(), currency.DashDelimiter)
+ if len(subCodes) == 3 {
+ if match, err := regexp.MatchString(`^[a-zA-Z0-9_]*$`, subCodes[1]); match && err == nil {
+ subCodes[1] = strings.ToLower(subCodes[1])
+ }
+ }
+ return pair.Base.String() + currency.DashDelimiter + strings.Join(subCodes, currency.DashDelimiter)
+}
diff --git a/exchanges/deribit/deribit_test.go b/exchanges/deribit/deribit_test.go
new file mode 100644
index 00000000..b726cdee
--- /dev/null
+++ b/exchanges/deribit/deribit_test.go
@@ -0,0 +1,3983 @@
+package deribit
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/common/key"
+ "github.com/thrasher-corp/gocryptotrader/core"
+ "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/fundingrate"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/futures"
+ "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/sharedtestvalues"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/trade"
+ testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
+ "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
+)
+
+// Please supply your own keys here to do authenticated endpoint testing
+const (
+ apiKey = ""
+ apiSecret = ""
+
+ canManipulateRealOrders = false
+ canManipulateAPIEndpoints = false
+ btcPerpInstrument = "BTC-PERPETUAL"
+ useTestNet = false
+)
+
+var (
+ d = &Deribit{}
+ optionsTradablePair, optionComboTradablePair, futureComboTradablePair currency.Pair
+ spotTradablePair = currency.NewPairWithDelimiter(currencyBTC, "USDC", "_")
+ futuresTradablePair = currency.NewPairWithDelimiter(currencyBTC, "PERPETUAL", "-")
+ assetTypeToPairsMap map[asset.Item]currency.Pair
+)
+
+func TestMain(m *testing.M) {
+ err := testexch.Setup(d)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if apiKey != "" && apiSecret != "" {
+ d.API.AuthenticatedSupport = true
+ d.API.AuthenticatedWebsocketSupport = true
+ d.SetCredentials(apiKey, apiSecret, "", "", "", "")
+ d.Websocket.SetCanUseAuthenticatedEndpoints(true)
+ }
+ if useTestNet {
+ deribitWebsocketAddress = "wss://test.deribit.com/ws" + deribitAPIVersion
+ err = d.Websocket.SetWebsocketURL(deribitWebsocketAddress, false, true)
+ if err != nil {
+ log.Fatal(err)
+ }
+ for k, v := range d.API.Endpoints.GetURLMap() {
+ v = strings.Replace(v, "www.deribit.com", "test.deribit.com", 1)
+ err = d.API.Endpoints.SetRunning(k, v)
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+ }
+
+ instantiateTradablePairs()
+ assetTypeToPairsMap = map[asset.Item]currency.Pair{
+ asset.Futures: futuresTradablePair,
+ asset.Spot: spotTradablePair,
+ asset.Options: optionsTradablePair,
+ asset.OptionCombo: optionComboTradablePair,
+ asset.FutureCombo: futureComboTradablePair,
+ }
+ setupWs()
+ os.Exit(m.Run())
+}
+
+func TestUpdateTicker(t *testing.T) {
+ t.Parallel()
+ _, err := d.UpdateTicker(context.Background(), currency.Pair{}, asset.Margin)
+ require.ErrorIs(t, err, asset.ErrNotSupported)
+
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.UpdateTicker(context.Background(), cp, assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestUpdateOrderbook(t *testing.T) {
+ t.Parallel()
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.UpdateOrderbook(context.Background(), cp, assetType)
+ require.NoErrorf(t, err, "%w for asset type: %v", err, assetType)
+ require.NotNil(t, result)
+ }
+}
+
+func TestGetHistoricTrades(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetHistoricTrades(context.Background(), futureComboTradablePair, asset.FutureCombo, time.Now().Add(-time.Minute*10), time.Now())
+ require.ErrorIs(t, err, asset.ErrNotSupported)
+ var result []trade.Data
+ for assetType, cp := range map[asset.Item]currency.Pair{asset.Spot: spotTradablePair, asset.Futures: futuresTradablePair} {
+ result, err = d.GetHistoricTrades(context.Background(), cp, assetType, time.Now().Add(-time.Minute*10), time.Now())
+ require.NoErrorf(t, err, "%w asset type: %v", err, assetType)
+ require.NotNilf(t, result, "Expected value not to be nil for asset type: %s", err, assetType.String())
+ }
+}
+
+func TestFetchRecentTrades(t *testing.T) {
+ t.Parallel()
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.GetRecentTrades(context.Background(), cp, assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGetHistoricCandles(t *testing.T) {
+ t.Parallel()
+ start := time.Now().Add(-time.Hour)
+ end := time.Now()
+ assetTypesToPairMap := map[asset.Item]struct {
+ Pair currency.Pair
+ Error error
+ }{
+ asset.Futures: {Pair: futuresTradablePair},
+ asset.Spot: {Pair: spotTradablePair},
+ asset.Options: {Pair: optionsTradablePair, Error: asset.ErrNotSupported},
+ asset.FutureCombo: {Pair: futureComboTradablePair, Error: asset.ErrNotSupported},
+ asset.OptionCombo: {Pair: optionComboTradablePair, Error: asset.ErrNotSupported},
+ }
+ for assetType, info := range assetTypesToPairMap {
+ resp, err := d.GetHistoricCandles(context.Background(), info.Pair, assetType, kline.FifteenMin, start, end)
+ require.ErrorIs(t, err, info.Error)
+ if info.Error == nil {
+ require.NotEmpty(t, resp)
+ }
+ }
+}
+
+func TestGetHistoricCandlesExtended(t *testing.T) {
+ t.Parallel()
+ start := time.Now().Add(-time.Hour * 24 * 90).Truncate(kline.OneDay.Duration()).UTC()
+ end := time.Now().UTC()
+ assetsToPairsMap := map[asset.Item]struct {
+ Pair currency.Pair
+ Error error
+ }{
+ asset.Futures: {Pair: futuresTradablePair},
+ asset.Spot: {Pair: spotTradablePair},
+ asset.Options: {Pair: optionsTradablePair, Error: asset.ErrNotSupported},
+ asset.FutureCombo: {Pair: futureComboTradablePair, Error: asset.ErrNotSupported},
+ asset.OptionCombo: {Pair: optionComboTradablePair, Error: asset.ErrNotSupported},
+ }
+ for assetType, instance := range assetsToPairsMap {
+ resp, err := d.GetHistoricCandlesExtended(context.Background(), instance.Pair, assetType, kline.OneDay, start, end)
+ require.ErrorIs(t, err, instance.Error)
+ if instance.Error == nil {
+ require.NotEmpty(t, resp)
+ }
+ }
+}
+
+func TestSubmitOrder(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ assetToPairStringMap := map[asset.Item]currency.Pair{
+ asset.Options: optionsTradablePair,
+ asset.FutureCombo: futureComboTradablePair,
+ asset.Futures: futuresTradablePair,
+ }
+ var result *order.SubmitResponse
+ var err error
+ var info *InstrumentData
+ for assetType, cp := range assetToPairStringMap {
+ info, err = d.GetInstrument(context.Background(), d.formatPairString(assetType, cp))
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+
+ result, err = d.SubmitOrder(
+ context.Background(),
+ &order.Submit{
+ Exchange: d.Name,
+ Price: 10,
+ Amount: info.ContractSize * 3,
+ Type: order.Limit,
+ AssetType: assetType,
+ Side: order.Buy,
+ Pair: cp,
+ },
+ )
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGetMarkPriceHistory(t *testing.T) {
+ t.Parallel()
+ var resp []MarkPriceHistory
+ err := json.Unmarshal([]byte(`[[1608142381229,0.5165791606037885],[1608142380231,0.5165737855432504],[1608142379227,0.5165768236356326]]`), &resp)
+ require.NoError(t, err)
+ _, err = d.GetMarkPriceHistory(context.Background(), "", time.Now().Add(-5*time.Minute), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result []MarkPriceHistory
+ for _, ps := range []string{
+ d.optionPairToString(optionsTradablePair),
+ spotTradablePair.String(),
+ btcPerpInstrument,
+ futureComboTradablePair.String(),
+ } {
+ result, err = d.GetMarkPriceHistory(context.Background(), ps, time.Now().Add(-5*time.Minute), time.Now())
+ require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps)
+ require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps)
+ }
+}
+
+func TestWSRetrieveMarkPriceHistory(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveMarkPriceHistory("", time.Now().Add(-4*time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result []MarkPriceHistory
+ for _, ps := range []string{
+ d.optionPairToString(optionsTradablePair),
+ spotTradablePair.String(),
+ btcPerpInstrument,
+ futureComboTradablePair.String(),
+ } {
+ result, err = d.WSRetrieveMarkPriceHistory(ps, time.Now().Add(-4*time.Hour), time.Now())
+ require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, ps)
+ require.NotNilf(t, result, "Expected value not to be nil for pair: %v", ps)
+ }
+}
+
+func TestGetBookSummaryByCurrency(t *testing.T) {
+ t.Parallel()
+ var response BookSummaryData
+ err := json.Unmarshal([]byte(`{ "volume_usd": 0, "volume": 0, "quote_currency": "USD",
+ "price_change": -11.1896349, "open_interest": 0, "mid_price": null, "mark_price": 3579.73, "low": null,
+ "last": null, "instrument_name": "BTC-22FEB19", "high": null, "estimated_delivery_price": 3579.73, "creation_timestamp": 1550230036440,
+ "bid_price": null, "base_currency": "BTC", "ask_price": null}`), &response)
+ require.NoError(t, err)
+ _, err = d.GetBookSummaryByCurrency(context.Background(), currency.EMPTYCODE, "future")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetBookSummaryByCurrency(context.Background(), currency.BTC, "option")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveBookBySummary(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveBookBySummary(currency.EMPTYCODE, "")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ result, err := d.WSRetrieveBookBySummary(currency.SOL, "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetBookSummaryByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetBookSummaryByInstrument(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result []BookSummaryData
+ for _, ps := range []string{
+ btcPerpInstrument,
+ spotTradablePair.String(),
+ futureComboTradablePair.String(),
+ d.optionPairToString(optionsTradablePair),
+ optionComboTradablePair.String(),
+ } {
+ result, err = d.GetBookSummaryByInstrument(context.Background(), ps)
+ require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps)
+ require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps)
+ }
+}
+
+func TestWSRetrieveBookSummaryByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveBookSummaryByInstrument("")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ var result []BookSummaryData
+ for _, ps := range []string{
+ btcPerpInstrument,
+ spotTradablePair.String(),
+ futureComboTradablePair.String(),
+ d.optionPairToString(optionsTradablePair),
+ optionComboTradablePair.String(),
+ } {
+ result, err = d.WSRetrieveBookSummaryByInstrument(ps)
+ require.NoErrorf(t, err, "expected nil, got %v for pair %s", err, ps)
+ require.NotNilf(t, result, "Expected result not to be nil for pair %s", ps)
+ }
+}
+
+func TestGetContractSize(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetContractSize(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ result, err := d.GetContractSize(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveContractSize(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveContractSize("")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.WSRetrieveContractSize(btcPerpInstrument)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetCurrencies(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetCurrencies(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveCurrencies(t *testing.T) {
+ t.Parallel()
+ result, err := d.WSRetrieveCurrencies()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetDeliveryPrices(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetDeliveryPrices(context.Background(), "", 0, 5)
+ require.ErrorIs(t, err, errUnsupportedIndexName)
+
+ result, err := d.GetDeliveryPrices(context.Background(), "btc_usd", 0, 5)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveDeliveryPrices(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveDeliveryPrices("", 0, 5)
+ require.ErrorIs(t, err, errUnsupportedIndexName)
+
+ result, err := d.WSRetrieveDeliveryPrices("btc_usd", 0, 5)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetFundingChartData(t *testing.T) {
+ t.Parallel()
+ // only for perpetual instruments
+ _, err := d.GetFundingChartData(context.Background(), "", "8h")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.GetFundingChartData(context.Background(), btcPerpInstrument, "8h")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestWSRetrieveFundingChartData(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveFundingChartData("", "8h")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.WSRetrieveFundingChartData(btcPerpInstrument, "8h")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetFundingRateHistory(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetFundingRateHistory(context.Background(), "", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.GetFundingRateHistory(context.Background(), btcPerpInstrument, time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveFundingRateHistory(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveFundingRateHistory("", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ result, err := d.WSRetrieveFundingRateHistory(btcPerpInstrument, time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetFundingRateValue(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetFundingRateValue(context.Background(), "", time.Time{}, time.Time{})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.GetFundingRateValue(context.Background(), btcPerpInstrument, time.Now(), time.Now().Add(-time.Hour*8))
+ require.ErrorIs(t, err, common.ErrStartAfterEnd)
+
+ result, err := d.GetFundingRateValue(context.Background(), btcPerpInstrument, time.Now().Add(-time.Hour*8), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveFundingRateValue(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveFundingRateValue(btcPerpInstrument, time.Now(), time.Now().Add(-time.Hour*8))
+ require.ErrorIs(t, err, common.ErrStartAfterEnd)
+
+ result, err := d.WSRetrieveFundingRateValue(btcPerpInstrument, time.Now().Add(-time.Hour*8), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetHistoricalVolatility(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetHistoricalVolatility(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetHistoricalVolatility(context.Background(), currency.BTC)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestWSRetrieveHistoricalVolatility(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveHistoricalVolatility(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.WSRetrieveHistoricalVolatility(currency.SOL)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetCurrencyIndexPrice(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetCurrencyIndexPrice(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ result, err := d.GetCurrencyIndexPrice(context.Background(), currency.BTC)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveCurrencyIndexPrice(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveCurrencyIndexPrice(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ result, err := d.WSRetrieveCurrencyIndexPrice(currency.BTC)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetIndexPrice(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetIndexPrice(context.Background(), "")
+ require.ErrorIs(t, err, errUnsupportedIndexName)
+ result, err := d.GetIndexPrice(context.Background(), "ada_usd")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveIndexPrice(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveIndexPrice("")
+ require.ErrorIs(t, err, errUnsupportedIndexName)
+ result, err := d.WSRetrieveIndexPrice("ada_usd")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetIndexPriceNames(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetIndexPriceNames(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveIndexPriceNames(t *testing.T) {
+ t.Parallel()
+ result, err := d.WSRetrieveIndexPriceNames()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetInstrumentData(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetInstrument(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result *InstrumentData
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.GetInstrument(context.Background(), d.formatPairString(assetType, cp))
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestWSRetrieveInstrumentData(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveInstrumentData("")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result *InstrumentData
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.WSRetrieveInstrumentData(d.formatPairString(assetType, cp))
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGetInstrumentsData(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetInstruments(context.Background(), currency.EMPTYCODE, "", false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetInstruments(context.Background(), currency.BTC, "", false)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.GetInstruments(context.Background(), currency.BTC, "", true)
+ require.NoError(t, err)
+ for a := range result {
+ require.Falsef(t, result[a].IsActive, "expected expired instrument, but got active instrument %s", result[a].InstrumentName)
+ }
+}
+func TestWSRetrieveInstrumentsData(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveInstrumentsData(currency.EMPTYCODE, "", false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.WSRetrieveInstrumentsData(currency.BTC, "", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetLastSettlementsByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastSettlementsByCurrency(context.Background(), currency.EMPTYCODE, "delivery", "5", 0, time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetLastSettlementsByCurrency(context.Background(), currency.BTC, "delivery", "5", 0, time.Now().Add(-time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestWSRetrieveLastSettlementsByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastSettlementsByCurrency(currency.EMPTYCODE, "delivery", "5", 0, time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.WSRetrieveLastSettlementsByCurrency(currency.BTC, "delivery", "5", 0, time.Now().Add(-time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveLastSettlementsByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastSettlementsByInstrument("", "settlement", "5", 0, time.Now().Add(-2*time.Hour))
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.WSRetrieveLastSettlementsByInstrument(d.formatFuturesTradablePair(futuresTradablePair), "settlement", "5", 0, time.Now().Add(-2*time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetLastSettlementsByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastSettlementsByInstrument(context.Background(), "", "settlement", "5", 0, time.Now().Add(-2*time.Hour))
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.GetLastSettlementsByInstrument(context.Background(), d.formatFuturesTradablePair(futuresTradablePair), "settlement", "5", 0, time.Now().Add(-2*time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetLastTradesByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastTradesByCurrency(context.Background(), currency.EMPTYCODE, "option", "36798", "36799", "asc", 0, true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetLastTradesByCurrency(context.Background(), currency.BTC, "option", "36798", "36799", "asc", 0, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveLastTradesByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastTradesByCurrency(currency.EMPTYCODE, "option", "36798", "36799", "asc", 0, true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.WSRetrieveLastTradesByCurrency(currency.BTC, "option", "36798", "36799", "asc", 0, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetLastTradesByCurrencyAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastTradesByCurrencyAndTime(context.Background(), currency.EMPTYCODE, "", "", 0, time.Now().Add(-8*time.Hour), time.Now())
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetLastTradesByCurrencyAndTime(context.Background(), currency.BTC, "", "", 0, time.Now().Add(-8*time.Hour), time.Now())
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.GetLastTradesByCurrencyAndTime(context.Background(), currency.BTC, "option", "asc", 25, time.Now().Add(-8*time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveLastTradesByCurrencyAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastTradesByCurrencyAndTime(currency.EMPTYCODE, "", "", 0, false, time.Now().Add(-8*time.Hour), time.Now())
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.WSRetrieveLastTradesByCurrencyAndTime(currency.BTC, "", "", 0, false, time.Now().Add(-8*time.Hour), time.Now())
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.WSRetrieveLastTradesByCurrencyAndTime(currency.BTC, "option", "asc", 25, false, time.Now().Add(-8*time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetLastTradesByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastTradesByInstrument(context.Background(), "", "", "", "", 0, false)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.GetLastTradesByInstrument(context.Background(), d.formatPairString(assetType, cp), "30500", "31500", "desc", 0, true)
+ require.NoErrorf(t, err, "expected %v, got %v currency asset %v pair %v", nil, err, assetType, cp)
+ require.NotNilf(t, result, "Expected value not to be nil for asset %v pair: %v", assetType, cp)
+ }
+}
+
+func TestWSRetrieveLastTradesByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastTradesByInstrument("", "", "", "", 0, false)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.WSRetrieveLastTradesByInstrument(d.formatPairString(assetType, cp), "30500", "31500", "desc", 0, true)
+ require.NoErrorf(t, err, "expected %v, got %v currency asset %v pair %v", nil, err, assetType, cp)
+ require.NotNilf(t, result, "Expected value not to be nil for asset %v pair: %v", assetType, cp)
+ }
+}
+
+func TestGetLastTradesByInstrumentAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastTradesByInstrumentAndTime(context.Background(), "", "", 0, time.Now().Add(-8*time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.GetLastTradesByInstrumentAndTime(context.Background(), d.formatPairString(assetType, cp), "", 0, time.Now().Add(-8*time.Hour), time.Now())
+ require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
+ require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
+ }
+}
+
+func TestWSRetrieveLastTradesByInstrumentAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastTradesByInstrumentAndTime("", "", 0, false, time.Now().Add(-8*time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.WSRetrieveLastTradesByInstrumentAndTime(d.formatPairString(assetType, cp), "", 0, true, time.Now().Add(-8*time.Hour), time.Now())
+ require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
+ require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
+ }
+}
+
+func TestGetOrderbookData(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOrderbook(context.Background(), "", 0)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result *Orderbook
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.GetOrderbook(context.Background(), d.formatPairString(assetType, cp), 0)
+ require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
+ require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
+ }
+}
+
+func TestWSRetrieveOrderbookData(t *testing.T) {
+ t.Parallel()
+ if !d.Websocket.IsConnected() {
+ t.Skip("websocket is not connected")
+ }
+ _, err := d.WSRetrieveOrderbookData("", 0)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ var result *Orderbook
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.WSRetrieveOrderbookData(d.formatPairString(assetType, cp), 0)
+ require.NoErrorf(t, err, "expected %v, got %v currency pair %v", nil, err, cp)
+ require.NotNilf(t, result, "Expected value not to be nil for pair: %v", cp)
+ }
+}
+
+func TestGetOrderbookByInstrumentID(t *testing.T) {
+ t.Parallel()
+ combos, err := d.GetComboIDs(context.Background(), currency.BTC, "")
+ require.NoError(t, err)
+ if len(combos) == 0 {
+ t.Skip("no combo instance found for currency BTC")
+ }
+ _, err = d.GetOrderbookByInstrumentID(context.Background(), 0, 50)
+ require.ErrorIs(t, err, errInvalidInstrumentID)
+
+ comboD, err := d.GetComboDetails(context.Background(), combos[0])
+ require.NoError(t, err)
+ require.NotNil(t, comboD)
+
+ result, err := d.GetOrderbookByInstrumentID(context.Background(), comboD.InstrumentID, 50)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOrderbookByInstrumentID(t *testing.T) {
+ t.Parallel()
+ combos, err := d.WSRetrieveComboIDs(currency.BTC, "")
+ require.NoError(t, err)
+ if len(combos) == 0 {
+ t.Skip("no combo instance found for currency BTC")
+ }
+ _, err = d.WSRetrieveOrderbookByInstrumentID(0, 50)
+ require.ErrorIs(t, err, errInvalidInstrumentID)
+ comboD, err := d.WSRetrieveComboDetails(combos[0])
+ require.NoError(t, err)
+ require.NotNil(t, comboD)
+
+ result, err := d.WSRetrieveOrderbookByInstrumentID(comboD.InstrumentID, 50)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetSupportedIndexNames(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetSupportedIndexNames(context.Background(), "derivative")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsRetrieveSupportedIndexNames(t *testing.T) {
+ t.Parallel()
+ result, err := d.WsRetrieveSupportedIndexNames("derivative")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetRequestForQuote(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetRequestForQuote(context.Background(), currency.EMPTYCODE, d.GetAssetKind(asset.Futures))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ result, err := d.GetRequestForQuote(context.Background(), currency.BTC, d.GetAssetKind(asset.Futures))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveRequestForQuote(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveRequestForQuote(currency.EMPTYCODE, d.GetAssetKind(asset.Futures))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ result, err := d.WSRetrieveRequestForQuote(currency.BTC, d.GetAssetKind(asset.Futures))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetTradeVolumes(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetTradeVolumes(context.Background(), false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveTradeVolumes(t *testing.T) {
+ t.Parallel()
+ result, err := d.WSRetrieveTradeVolumes(false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetTradingViewChartData(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetTradingViewChart(context.Background(), "", "60", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.GetTradingViewChart(context.Background(), btcPerpInstrument, "60", time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.GetTradingViewChart(context.Background(), spotTradablePair.String(), "60", time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrievesTradingViewChartData(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrievesTradingViewChartData("", "60", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ result, err := d.WSRetrievesTradingViewChartData(btcPerpInstrument, "60", time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.WSRetrievesTradingViewChartData(spotTradablePair.String(), "60", time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetVolatilityIndexData(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetVolatilityIndex(context.Background(), currency.EMPTYCODE, "60", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.GetVolatilityIndex(context.Background(), currency.BTC, "", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errResolutionNotSet)
+ _, err = d.GetVolatilityIndex(context.Background(), currency.BTC, "60", time.Now(), time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, common.ErrStartAfterEnd)
+
+ result, err := d.GetVolatilityIndex(context.Background(), currency.BTC, "60", time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveVolatilityIndexData(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveVolatilityIndexData(currency.EMPTYCODE, "60", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSRetrieveVolatilityIndexData(currency.BTC, "", time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errResolutionNotSet)
+ _, err = d.WSRetrieveVolatilityIndexData(currency.BTC, "60", time.Now(), time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, common.ErrStartAfterEnd)
+
+ result, err := d.WSRetrieveVolatilityIndexData(currency.BTC, "60", time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetPublicTicker(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetPublicTicker(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.GetPublicTicker(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrievePublicTicker(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrievePublicTicker("")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ result, err := d.WSRetrievePublicTicker(btcPerpInstrument)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetAccountSummary(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetAccountSummary(context.Background(), currency.EMPTYCODE, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetAccountSummary(context.Background(), currency.BTC, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveAccountSummary(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveAccountSummary(currency.EMPTYCODE, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveAccountSummary(currency.BTC, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestCancelTransferByID(t *testing.T) {
+ t.Parallel()
+ _, err := d.CancelTransferByID(context.Background(), currency.EMPTYCODE, "", 23487)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.CancelTransferByID(context.Background(), currency.BTC, "", 0)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.CancelTransferByID(context.Background(), currency.BTC, "", 23487)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSCancelTransferByID(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSCancelTransferByID(currency.EMPTYCODE, "", 23487)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSCancelTransferByID(currency.BTC, "", 0)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSCancelTransferByID(currency.BTC, "", 23487)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getTransferResponseJSON = `{"count": 2, "data":[{"amount": 0.2, "created_timestamp": 1550579457727, "currency": "BTC", "direction": "payment", "id": 2, "other_side": "2MzyQc5Tkik61kJbEpJV5D5H9VfWHZK9Sgy", "state": "prepared", "type": "user", "updated_timestamp": 1550579457727 }, { "amount": 0.3, "created_timestamp": 1550579255800, "currency": "BTC", "direction": "payment", "id": 1, "other_side": "new_user_1_1", "state": "confirmed", "type": "subaccount", "updated_timestamp": 1550579255800 } ] }`
+
+func TestGetTransfers(t *testing.T) {
+ t.Parallel()
+ var resp *TransfersData
+ err := json.Unmarshal([]byte(getTransferResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetTransfers(context.Background(), currency.EMPTYCODE, 0, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetTransfers(context.Background(), currency.BTC, 0, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveTransfers(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveTransfers(currency.EMPTYCODE, 0, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveTransfers(currency.BTC, 0, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const cancelWithdrawlPushDataJSON = `{"address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", "amount": 0.5, "confirmed_timestamp": null, "created_timestamp": 1550571443070, "currency": "BTC", "fee": 0.0001, "id": 1, "priority": 0.15, "state": "cancelled", "transaction_id": null, "updated_timestamp": 1550571443070 }`
+
+func TestCancelWithdrawal(t *testing.T) {
+ t.Parallel()
+ var resp *CancelWithdrawalData
+ err := json.Unmarshal([]byte(cancelWithdrawlPushDataJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.CancelWithdrawal(context.Background(), currency.EMPTYCODE, 123844)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.CancelWithdrawal(context.Background(), currency.BTC, 0)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.CancelWithdrawal(context.Background(), currency.BTC, 123844)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSCancelWithdrawal(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSCancelWithdrawal(currency.EMPTYCODE, 123844)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSCancelWithdrawal(currency.BTC, 0)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSCancelWithdrawal(currency.BTC, 123844)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestCreateDepositAddress(t *testing.T) {
+ t.Parallel()
+ _, err := d.CreateDepositAddress(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.CreateDepositAddress(context.Background(), currency.SOL)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSCreateDepositAddress(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSCreateDepositAddress(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSCreateDepositAddress(currency.SOL)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetCurrentDepositAddress(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetCurrentDepositAddress(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetCurrentDepositAddress(context.Background(), currency.ETH)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveCurrentDepositAddress(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveCurrentDepositAddress(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveCurrentDepositAddress(currency.ETH)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getDepositPushDataJSON = `{"count": 1, "data": [ { "address": "2N35qDKDY22zmJq9eSyiAerMD4enJ1xx6ax", "amount": 5, "currency": "BTC", "received_timestamp": 1549295017670, "state": "completed", "transaction_id": "230669110fdaf0a0dbcdc079b6b8b43d5af29cc73683835b9bc6b3406c065fda", "updated_timestamp": 1549295130159 } ] }`
+
+func TestGetDeposits(t *testing.T) {
+ t.Parallel()
+ var resp *DepositsData
+ err := json.Unmarshal([]byte(getDepositPushDataJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetDeposits(context.Background(), currency.EMPTYCODE, 25, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetDeposits(context.Background(), currency.BTC, 25, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveDeposits(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveDeposits(currency.EMPTYCODE, 25, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveDeposits(currency.BTC, 25, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getWithdrawalResponseJSON = `{"count": 1, "data": [ { "address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", "amount": 0.5, "confirmed_timestamp": null, "created_timestamp": 1550571443070, "currency": "BTC", "fee": 0.0001, "id": 1, "priority": 0.15, "state": "unconfirmed", "transaction_id": null, "updated_timestamp": 1550571443070 } ] }`
+
+func TestGetWithdrawals(t *testing.T) {
+ t.Parallel()
+ var resp *WithdrawalsData
+ err := json.Unmarshal([]byte(getWithdrawalResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetWithdrawals(context.Background(), currency.EMPTYCODE, 25, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetWithdrawals(context.Background(), currency.BTC, 25, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveWithdrawals(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveWithdrawals(currency.EMPTYCODE, 25, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveWithdrawals(currency.BTC, 25, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitTransferBetweenSubAccounts(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitTransferBetweenSubAccounts(context.Background(), currency.EMPTYCODE, 12345, 2, "")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.SubmitTransferBetweenSubAccounts(context.Background(), currency.EURR, 0, 2, "")
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.SubmitTransferBetweenSubAccounts(context.Background(), currency.EURR, 12345, -1, "")
+ require.ErrorIs(t, err, errInvalidDestinationID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitTransferBetweenSubAccounts(context.Background(), currency.EURR, 12345, 4, "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsSubmitTransferBetweenSubAccounts(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsSubmitTransferBetweenSubAccounts(currency.EMPTYCODE, 12345, 2, "")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WsSubmitTransferBetweenSubAccounts(currency.EURR, 0, 2, "")
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WsSubmitTransferBetweenSubAccounts(currency.EURR, 12345, -1, "")
+ require.ErrorIs(t, err, errInvalidDestinationID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WsSubmitTransferBetweenSubAccounts(currency.EURR, 12345, 2, "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitTransferToSubAccount(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitTransferToSubAccount(context.Background(), currency.EMPTYCODE, 0.01, 13434)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.SubmitTransferToSubAccount(context.Background(), currency.BTC, 0, 13434)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.SubmitTransferToSubAccount(context.Background(), currency.BTC, 0.01, 0)
+ require.ErrorIs(t, err, errInvalidDestinationID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitTransferToSubAccount(context.Background(), currency.BTC, 0.01, 13434)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitTransferToSubAccount(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitTransferToSubAccount(currency.EMPTYCODE, 0.01, 13434)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSSubmitTransferToSubAccount(currency.BTC, 0, 13434)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WSSubmitTransferToSubAccount(currency.BTC, 0.01, 0)
+ require.ErrorIs(t, err, errInvalidDestinationID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitTransferToSubAccount(currency.BTC, 0.01, 13434)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitTransferToUser(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitTransferToUser(context.Background(), currency.EMPTYCODE, "", "0x4aa0753d798d668056920094d65321a8e8913e26", 0.001)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.SubmitTransferToUser(context.Background(), currency.BTC, "", "0x4aa0753d798d668056920094d65321a8e8913e26", 0)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.SubmitTransferToUser(context.Background(), currency.BTC, "", "", 0.001)
+ require.ErrorIs(t, err, errInvalidCryptoAddress)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitTransferToUser(context.Background(), currency.BTC, "", "13434", 0.001)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitTransferToUser(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitTransferToUser(currency.EMPTYCODE, "", "0x4aa0753d798d668056920094d65321a8e8913e26", 0.001)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSSubmitTransferToUser(currency.BTC, "", "0x4aa0753d798d668056920094d65321a8e8913e26", 0)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WSSubmitTransferToUser(currency.BTC, "", "", 0.001)
+ require.ErrorIs(t, err, errInvalidCryptoAddress)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitTransferToUser(currency.BTC, "", "", 0.001)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const submitWithdrawalResponseJSON = `{"address": "2NBqqD5GRJ8wHy1PYyCXTe9ke5226FhavBz", "amount": 0.4, "confirmed_timestamp": null, "created_timestamp": 1550574558607, "currency": "BTC", "fee": 0.0001, "id": 4, "priority": 1, "state": "unconfirmed", "transaction_id": null, "updated_timestamp": 1550574558607 }`
+
+func TestSubmitWithdraw(t *testing.T) {
+ t.Parallel()
+ var resp *WithdrawData
+ err := json.Unmarshal([]byte(submitWithdrawalResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.SubmitWithdraw(context.Background(), currency.EMPTYCODE, core.BitcoinDonationAddress, "", 0.001)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.SubmitWithdraw(context.Background(), currency.BTC, core.BitcoinDonationAddress, "", 0)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.SubmitWithdraw(context.Background(), currency.BTC, "", "", 0.001)
+ require.ErrorIs(t, err, errInvalidCryptoAddress)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitWithdraw(context.Background(), currency.BTC, core.BitcoinDonationAddress, "", 0.001)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitWithdraw(t *testing.T) {
+ _, err := d.WSSubmitWithdraw(currency.EMPTYCODE, core.BitcoinDonationAddress, "", 0.001)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSSubmitWithdraw(currency.BTC, core.BitcoinDonationAddress, "", 0)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WSSubmitWithdraw(currency.BTC, "", "", 0.001)
+ require.ErrorIs(t, err, errInvalidCryptoAddress)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitWithdraw(currency.BTC, core.BitcoinDonationAddress, "", 0.001)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetAnnouncements(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetAnnouncements(context.Background(), time.Now(), 5)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveAnnouncements(t *testing.T) {
+ t.Parallel()
+ result, err := d.WSRetrieveAnnouncements(time.Now(), 5)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetPublicPortfolioMargins(t *testing.T) {
+ info, err := d.GetInstrument(context.Background(), "BTC-PERPETUAL")
+ require.NoError(t, err)
+ _, err = d.GetPublicPortfolioMargins(context.Background(), currency.EMPTYCODE, map[string]float64{"BTC-PERPETUAL": info.ContractSize * 2})
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ result, err := d.GetPublicPortfolioMargins(context.Background(), currency.BTC, map[string]float64{"BTC-PERPETUAL": info.ContractSize * 2})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetAccessLog(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetAccessLog(context.Background(), 0, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveAccessLog(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveAccessLog(0, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestChangeAPIKeyName(t *testing.T) {
+ t.Parallel()
+ _, err := d.ChangeAPIKeyName(context.Background(), 0, "TestKey123")
+ require.ErrorIs(t, err, errInvalidID)
+ _, err = d.ChangeAPIKeyName(context.Background(), 2, "TestKey123$")
+ require.ErrorIs(t, err, errUnacceptableAPIKey)
+ _, err = d.ChangeAPIKeyName(context.Background(), 2, "#$#")
+ require.ErrorIs(t, err, errUnacceptableAPIKey)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.ChangeAPIKeyName(context.Background(), 1, "TestKey123")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSChangeAPIKeyName(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSChangeAPIKeyName(0, "TestKey123")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WSChangeAPIKeyName(1, "TestKey123")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestChangeMarginModel(t *testing.T) {
+ t.Parallel()
+ _, err := d.ChangeMarginModel(context.Background(), 2, "", false)
+ require.ErrorIs(t, err, errInvalidMarginModel)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.ChangeMarginModel(context.Background(), 2, "segregated_pm", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsChangeMarginModel(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsChangeMarginModel(2, "", false)
+ require.ErrorIs(t, err, errInvalidMarginModel)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+
+ result, err := d.WsChangeMarginModel(2, "segregated_pm", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestChangeScopeInAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.ChangeScopeInAPIKey(context.Background(), -1, "account:read_write")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.ChangeScopeInAPIKey(context.Background(), 1, "account:read_write")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSChangeScopeInAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSChangeScopeInAPIKey(0, "account:read_write")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WSChangeScopeInAPIKey(1, "account:read_write")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestChangeSubAccountName(t *testing.T) {
+ t.Parallel()
+ err := d.ChangeSubAccountName(context.Background(), 0, "new_sub")
+ require.ErrorIs(t, err, errInvalidID)
+ err = d.ChangeSubAccountName(context.Background(), 312313, "")
+ require.ErrorIs(t, err, errInvalidUsername)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.ChangeSubAccountName(context.Background(), 1, "new_sub")
+ assert.NoError(t, err)
+}
+
+func TestWSChangeSubAccountName(t *testing.T) {
+ t.Parallel()
+ err := d.WSChangeSubAccountName(0, "new_sub")
+ require.ErrorIs(t, err, errInvalidID)
+ err = d.WSChangeSubAccountName(312313, "")
+ require.ErrorIs(t, err, errInvalidUsername)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.WSChangeSubAccountName(1, "new_sub")
+ assert.NoError(t, err)
+}
+
+func TestCreateAPIKey(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.CreateAPIKey(context.Background(), "account:read_write", "new_sub", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSCreateAPIKey(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WSCreateAPIKey("account:read_write", "new_sub", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestCreateSubAccount(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.CreateSubAccount(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSCreateSubAccount(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSCreateSubAccount()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestDisableAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.DisableAPIKey(context.Background(), 0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.DisableAPIKey(context.Background(), 1)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSDisableAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSDisableAPIKey(0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WSDisableAPIKey(1)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestEditAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.EditAPIKey(context.Background(), 0, "trade", "", false, []string{"read", "read_write"}, []string{})
+ require.ErrorIs(t, err, errInvalidAPIKeyID)
+ _, err = d.EditAPIKey(context.Background(), 1234, "", "", false, []string{"read", "read_write"}, []string{})
+ require.ErrorIs(t, err, errMaxScopeIsRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.EditAPIKey(context.Background(), 1234, "trade", "", false, []string{"read", "read_write"}, []string{})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsEditAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsEditAPIKey(0, "trade", "", false, []string{"read", "read_write"}, []string{})
+ require.ErrorIs(t, err, errInvalidAPIKeyID)
+ _, err = d.WsEditAPIKey(1234, "", "", false, []string{"read", "read_write"}, []string{})
+ require.ErrorIs(t, err, errMaxScopeIsRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WsEditAPIKey(1234, "trade", "", false, []string{"read", "read_write"}, []string{})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestEnableAffiliateProgram(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err := d.EnableAffiliateProgram(context.Background())
+ assert.NoError(t, err)
+}
+
+func TestWSEnableAffiliateProgram(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err := d.WSEnableAffiliateProgram()
+ assert.NoError(t, err)
+}
+
+func TestEnableAPIKey(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.EnableAPIKey(context.Background(), 1)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSEnableAPIKey(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WSEnableAPIKey(1)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetAffiliateProgramInfo(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetAffiliateProgramInfo(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveAffiliateProgramInfo(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveAffiliateProgramInfo()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetEmailLanguage(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetEmailLanguage(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveEmailLanguage(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveEmailLanguage()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetNewAnnouncements(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetNewAnnouncements(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveNewAnnouncements(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveNewAnnouncements()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetPrivatePortfolioMargins(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetPrivatePortfolioMargins(context.Background(), currency.EMPTYCODE, false, nil)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetPrivatePortfolioMargins(context.Background(), currency.BTC, false, nil)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsRetrievePrivatePortfolioMargins(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrievePrivatePortfolioMargins(currency.EMPTYCODE, false, nil)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrievePrivatePortfolioMargins(currency.BTC, false, nil)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetPosition(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetPosition(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetPosition(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrievePosition(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrievePosition("")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrievePosition(btcPerpInstrument)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetSubAccounts(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetSubAccounts(context.Background(), false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveSubAccounts(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveSubAccounts(false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetSubAccountDetails(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetSubAccountDetails(context.Background(), currency.EMPTYCODE, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetSubAccountDetails(context.Background(), currency.BTC, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveSubAccountDetails(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveSubAccountDetails(currency.EMPTYCODE, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveSubAccountDetails(currency.BTC, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetPositions(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetPositions(context.Background(), currency.EMPTYCODE, "option")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetPositions(context.Background(), currency.BTC, "option")
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.GetPositions(context.Background(), currency.ETH, "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrievePositions(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrievePositions(currency.EMPTYCODE, "option")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrievePositions(currency.BTC, "option")
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.WSRetrievePositions(currency.ETH, "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getTransactionLogResponseJSON = `{"logs": [ { "username": "TestUser", "user_seq": 6009, "user_id": 7, "type": "transfer", "trade_id": null, "timestamp": 1613659830333, "side": "-", "price": null, "position": null, "order_id": null, "interest_pl": null, "instrument_name": null, "info": { "transfer_type": "subaccount", "other_user_id": 27, "other_user": "Subaccount" }, "id": 61312, "equity": 3000.9275869, "currency": "BTC", "commission": 0, "change": -2.5, "cashflow": -2.5, "balance": 3001.22270418 } ], "continuation": 61282 }`
+
+func TestGetTransactionLog(t *testing.T) {
+ t.Parallel()
+ var resp *TransactionsData
+ err := json.Unmarshal([]byte(getTransactionLogResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetTransactionLog(context.Background(), currency.EMPTYCODE, "trade", time.Now().Add(-24*time.Hour), time.Now(), 5, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetTransactionLog(context.Background(), currency.BTC, "trade", time.Now().Add(-24*time.Hour), time.Now(), 5, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveTransactionLog(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveTransactionLog(currency.EMPTYCODE, "trade", time.Now().Add(-24*time.Hour), time.Now(), 5, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveTransactionLog(currency.BTC, "trade", time.Now().Add(-24*time.Hour), time.Now(), 5, 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetUserLocks(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserLocks(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveUserLocks(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveUserLocks()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestListAPIKeys(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.ListAPIKeys(context.Background(), "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSListAPIKeys(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSListAPIKeys("")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetCustodyAccounts(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetCustodyAccounts(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetCustodyAccounts(context.Background(), currency.BTC)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsRetrieveCustodyAccounts(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsRetrieveCustodyAccounts(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WsRetrieveCustodyAccounts(currency.BTC)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestRemoveAPIKey(t *testing.T) {
+ t.Parallel()
+ err := d.RemoveAPIKey(context.Background(), 0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ err = d.RemoveAPIKey(context.Background(), 1)
+ assert.NoError(t, err)
+}
+
+func TestWSRemoveAPIKey(t *testing.T) {
+ t.Parallel()
+ err := d.WSRemoveAPIKey(0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ err = d.WSRemoveAPIKey(1)
+ assert.NoError(t, err)
+}
+
+func TestRemoveSubAccount(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ err := d.RemoveSubAccount(context.Background(), 1)
+ assert.NoError(t, err)
+}
+
+func TestWSRemoveSubAccount(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ err := d.WSRemoveSubAccount(1)
+ assert.NoError(t, err)
+}
+
+func TestResetAPIKey(t *testing.T) {
+ t.Parallel()
+ _, err := d.ResetAPIKey(context.Background(), 0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.ResetAPIKey(context.Background(), 1)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSResetAPIKey(t *testing.T) {
+ t.Parallel()
+ err := d.WSResetAPIKey(0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ err = d.WSResetAPIKey(1)
+ assert.NoError(t, err)
+}
+
+func TestSetAnnouncementAsRead(t *testing.T) {
+ t.Parallel()
+ err := d.SetAnnouncementAsRead(context.Background(), 0)
+ require.ErrorIs(t, err, errInvalidID)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.SetAnnouncementAsRead(context.Background(), 1)
+ assert.NoError(t, err)
+}
+
+func TestSetEmailForSubAccount(t *testing.T) {
+ t.Parallel()
+ err := d.SetEmailForSubAccount(context.Background(), 0, "wrongemail@wrongemail.com")
+ require.ErrorIs(t, err, errInvalidID)
+ err = d.SetEmailForSubAccount(context.Background(), 1, "")
+ require.ErrorIs(t, err, errInvalidEmailAddress)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.SetEmailForSubAccount(context.Background(), 1, "wrongemail@wrongemail.com")
+ assert.NoError(t, err)
+}
+
+func TestWSSetEmailForSubAccount(t *testing.T) {
+ t.Parallel()
+ err := d.WSSetEmailForSubAccount(0, "wrongemail@wrongemail.com")
+ require.ErrorIs(t, err, errInvalidID)
+ err = d.WSSetEmailForSubAccount(1, "")
+ require.ErrorIs(t, err, errInvalidEmailAddress)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.WSSetEmailForSubAccount(1, "wrongemail@wrongemail.com")
+ assert.NoError(t, err)
+}
+
+func TestSetEmailLanguage(t *testing.T) {
+ t.Parallel()
+ err := d.SetEmailLanguage(context.Background(), "")
+ require.ErrorIs(t, err, errLanguageIsRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.SetEmailLanguage(context.Background(), "en")
+ assert.NoError(t, err)
+}
+
+func TestWSSetEmailLanguage(t *testing.T) {
+ t.Parallel()
+ err := d.WSSetEmailLanguage("")
+ require.ErrorIs(t, err, errLanguageIsRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.WSSetEmailLanguage("en")
+ assert.NoError(t, err)
+}
+
+func TestSetSelfTradingConfig(t *testing.T) {
+ t.Parallel()
+ _, err := d.SetSelfTradingConfig(context.Background(), "", false)
+ require.ErrorIs(t, err, errTradeModeIsRequired)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SetSelfTradingConfig(context.Background(), "reject_taker", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsSetSelfTradingConfig(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsSetSelfTradingConfig("", false)
+ require.ErrorIs(t, err, errTradeModeIsRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WsSetSelfTradingConfig("reject_taker", false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestToggleNotificationsFromSubAccount(t *testing.T) {
+ t.Parallel()
+ err := d.ToggleNotificationsFromSubAccount(context.Background(), 0, false)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.ToggleNotificationsFromSubAccount(context.Background(), 1, false)
+ assert.NoError(t, err)
+}
+
+func TestWSToggleNotificationsFromSubAccount(t *testing.T) {
+ t.Parallel()
+ err := d.WSToggleNotificationsFromSubAccount(0, false)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.WSToggleNotificationsFromSubAccount(1, false)
+ assert.NoError(t, err)
+}
+
+func TestTogglePortfolioMargining(t *testing.T) {
+ t.Parallel()
+ _, err := d.TogglePortfolioMargining(context.Background(), 0, false, false)
+ require.ErrorIs(t, err, errUserIDRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.TogglePortfolioMargining(context.Background(), 1234, false, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSTogglePortfolioMargining(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSTogglePortfolioMargining(0, false, false)
+ require.ErrorIs(t, err, errUserIDRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSTogglePortfolioMargining(1234, false, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestToggleSubAccountLogin(t *testing.T) {
+ t.Parallel()
+ err := d.ToggleSubAccountLogin(context.Background(), -1, false)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.ToggleSubAccountLogin(context.Background(), 1, false)
+ assert.NoError(t, err)
+}
+
+func TestWSToggleSubAccountLogin(t *testing.T) {
+ t.Parallel()
+ err := d.WSToggleSubAccountLogin(-1, false)
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err = d.WSToggleSubAccountLogin(1, false)
+ assert.NoError(t, err)
+}
+
+func TestSubmitBuy(t *testing.T) {
+ t.Parallel()
+ pairs, err := d.GetEnabledPairs(asset.Futures)
+ require.NoError(t, err)
+ _, err = d.SubmitBuy(context.Background(), &OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ _, err = d.SubmitBuy(context.Background(), &OrderBuyAndSellParams{
+ Instrument: "", OrderType: "limit",
+ Label: "testOrder", TimeInForce: "",
+ Trigger: "", Advanced: "",
+ Amount: 30, Price: 500000,
+ MaxShow: 0, TriggerPrice: 0})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitBuy(context.Background(), &OrderBuyAndSellParams{
+ Instrument: pairs[0].String(), OrderType: "limit",
+ Label: "testOrder", TimeInForce: "",
+ Trigger: "", Advanced: "",
+ Amount: 30, Price: 500000,
+ MaxShow: 0, TriggerPrice: 0,
+ PostOnly: false, RejectPostOnly: false,
+ ReduceOnly: false, MMP: false})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitBuy(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitBuy(&OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ _, err = d.WSSubmitBuy(&OrderBuyAndSellParams{
+ Instrument: "", OrderType: "limit",
+ Label: "testOrder", TimeInForce: "",
+ Trigger: "", Advanced: "",
+ Amount: 30, Price: 500000,
+ MaxShow: 0, TriggerPrice: 0})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitBuy(&OrderBuyAndSellParams{
+ Instrument: btcPerpInstrument, OrderType: "limit",
+ Label: "testOrder", TimeInForce: "",
+ Trigger: "", Advanced: "",
+ Amount: 30, Price: 500000,
+ MaxShow: 0, TriggerPrice: 0,
+ PostOnly: false, RejectPostOnly: false,
+ ReduceOnly: false, MMP: false})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitSell(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitSell(context.Background(), &OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ _, err = d.SubmitSell(context.Background(), &OrderBuyAndSellParams{OrderType: "limit", Label: "testOrder", TimeInForce: "", Trigger: "", Advanced: "", Amount: info.ContractSize * 3, Price: 500000, MaxShow: 0, TriggerPrice: 0, PostOnly: false, RejectPostOnly: false, ReduceOnly: false, MMP: false})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitSell(context.Background(), &OrderBuyAndSellParams{Instrument: btcPerpInstrument, OrderType: "limit", Label: "testOrder", TimeInForce: "", Trigger: "", Advanced: "", Amount: info.ContractSize * 3, Price: 500000, MaxShow: 0, TriggerPrice: 0, PostOnly: false, RejectPostOnly: false, ReduceOnly: false, MMP: false})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestWSSubmitSell(t *testing.T) {
+ t.Parallel()
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ _, err = d.WSSubmitSell(&OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ _, err = d.WSSubmitSell(&OrderBuyAndSellParams{
+ Instrument: "", OrderType: "limit",
+ Label: "testOrder", TimeInForce: "",
+ Trigger: "", Advanced: "", Amount: info.ContractSize * 3,
+ Price: 500000, MaxShow: 0, TriggerPrice: 0, PostOnly: false,
+ RejectPostOnly: false, ReduceOnly: false, MMP: false})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitSell(&OrderBuyAndSellParams{
+ Instrument: btcPerpInstrument, OrderType: "limit",
+ Label: "testOrder", TimeInForce: "",
+ Trigger: "", Advanced: "", Amount: info.ContractSize * 3,
+ Price: 500000, MaxShow: 0, TriggerPrice: 0, PostOnly: false,
+ RejectPostOnly: false, ReduceOnly: false, MMP: false})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestEditOrderByLabel(t *testing.T) {
+ t.Parallel()
+ _, err := d.EditOrderByLabel(context.Background(), &OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ _, err = d.EditOrderByLabel(context.Background(), &OrderBuyAndSellParams{Label: "incorrectUserLabel", Instrument: "",
+ Advanced: "", Amount: 1, Price: 30000, TriggerPrice: 0, PostOnly: false, ReduceOnly: false, RejectPostOnly: false, MMP: false})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.EditOrderByLabel(context.Background(), &OrderBuyAndSellParams{Label: "incorrectUserLabel", Instrument: btcPerpInstrument,
+ Advanced: "", Amount: 0, Price: 30000, TriggerPrice: 0, PostOnly: false, ReduceOnly: false, RejectPostOnly: false, MMP: false})
+ require.ErrorIs(t, err, errInvalidAmount)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.EditOrderByLabel(context.Background(), &OrderBuyAndSellParams{Label: "incorrectUserLabel", Instrument: btcPerpInstrument,
+ Advanced: "", Amount: 1, Price: 30000, TriggerPrice: 0, PostOnly: false, ReduceOnly: false, RejectPostOnly: false, MMP: false})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSEditOrderByLabel(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSEditOrderByLabel(&OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ _, err = d.WSEditOrderByLabel(&OrderBuyAndSellParams{Label: "incorrectUserLabel", Instrument: "",
+ Advanced: "", Amount: 1, Price: 30000, TriggerPrice: 0, PostOnly: false, ReduceOnly: false, RejectPostOnly: false, MMP: false})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.WSEditOrderByLabel(&OrderBuyAndSellParams{Label: "incorrectUserLabel", Instrument: btcPerpInstrument,
+ Advanced: "", Amount: 0, Price: 30000, TriggerPrice: 0, PostOnly: false, ReduceOnly: false, RejectPostOnly: false, MMP: false})
+ require.ErrorIs(t, err, errInvalidAmount)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSEditOrderByLabel(&OrderBuyAndSellParams{Label: "incorrectUserLabel", Instrument: btcPerpInstrument,
+ Advanced: "", Amount: 1, Price: 30000, TriggerPrice: 0, PostOnly: false, ReduceOnly: false, RejectPostOnly: false, MMP: false})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const submitCancelResponseJSON = `{"triggered": false, "trigger": "index_price", "time_in_force": "good_til_cancelled", "trigger_price": 144.73, "reduce_only": false, "profit_loss": 0, "price": "1234", "post_only": false, "order_type": "stop_market", "order_state": "untriggered", "order_id": "ETH-SLIS-12", "max_show": 5, "last_update_timestamp": 1550575961291, "label": "", "is_liquidation": false, "instrument_name": "ETH-PERPETUAL", "direction": "sell", "creation_timestamp": 1550575961291, "api": false, "amount": 5 }`
+
+func TestSubmitCancel(t *testing.T) {
+ t.Parallel()
+ var resp *PrivateCancelData
+ err := json.Unmarshal([]byte(submitCancelResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.SubmitCancel(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancel(context.Background(), "incorrectID")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitCancel(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitCancel("")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitCancel("incorrectID")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitCancelAll(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancelAll(context.Background(), false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitCancelAll(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitCancelAll(true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitCancelAllByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitCancelAllByCurrency(context.Background(), currency.EMPTYCODE, "option", "", true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancelAllByCurrency(context.Background(), currency.BTC, "option", "", true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitCancelAllByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitCancelAllByCurrency(currency.EMPTYCODE, "option", "", true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitCancelAllByCurrency(currency.BTC, "option", "", true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitCancelAllByKind(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitCancelAllByKind(context.Background(), currency.EMPTYCODE, "option_combo", "trigger_all", true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancelAllByKind(context.Background(), currency.ETH, "option_combo", "trigger_all", true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsSubmitCancelAllByKind(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsSubmitCancelAllByKind(currency.EMPTYCODE, "option_combo", "trigger_all", true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WsSubmitCancelAllByKind(currency.ETH, "option_combo", "trigger_all", true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitCancelAllByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitCancelAllByInstrument(context.Background(), "", "all", true, true)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancelAllByInstrument(context.Background(), btcPerpInstrument, "all", true, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitCancelAllByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitCancelAllByInstrument("", "all", true, true)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitCancelAllByInstrument(btcPerpInstrument, "all", true, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitCancelByLabel(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancelByLabel(context.Background(), "incorrectOrderLabel", currency.EMPTYCODE, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitCancelByLabel(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitCancelByLabel("incorrectOrderLabel", currency.EMPTYCODE, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitCancelQuotes(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitCancelQuotes(context.Background(), currency.EMPTYCODE, 0, 0, "all", "", futuresTradablePair.String(), "future", true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitCancelQuotes(context.Background(), currency.BTC, 0, 0, "all", "", futuresTradablePair.String(), "future", true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitCancelQuotes(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitCancelQuotes(currency.EMPTYCODE, 0, 0, "all", "", futuresTradablePair.String(), "future", true)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitCancelQuotes(currency.BTC, 0, 0, "all", "", futuresTradablePair.String(), "future", true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSubmitClosePosition(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitClosePosition(context.Background(), "", "limit", 35000)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitClosePosition(context.Background(), d.formatFuturesTradablePair(futuresTradablePair), "limit", 35000)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitClosePosition(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitClosePosition("", "limit", 35000)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitClosePosition(d.formatFuturesTradablePair(futuresTradablePair), "limit", 35000)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetMargins(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetMargins(context.Background(), "", 5, 35000)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.GetMargins(context.Background(), d.formatFuturesTradablePair(futuresTradablePair), 0, 35000)
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.GetMargins(context.Background(), d.formatFuturesTradablePair(futuresTradablePair), 5, -1)
+ require.ErrorIs(t, err, errInvalidPrice)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetMargins(context.Background(), d.formatFuturesTradablePair(futuresTradablePair), 5, 35000)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveMargins(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveMargins("", 5, 35000)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveMargins(d.formatFuturesTradablePair(futuresTradablePair), 5, 35000)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetMMPConfig(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetMMPConfig(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetMMPConfig(context.Background(), currency.ETH)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveMMPConfig(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveMMPConfig(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveMMPConfig(currency.ETH)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getOpenOrdersByCurrencyResponseJSON = `[{ "time_in_force": "good_til_cancelled", "reduce_only": false, "profit_loss": 0, "price": 0.0028, "post_only": false, "order_type": "limit", "order_state": "open", "order_id": "146062", "max_show": 10, "last_update_timestamp": 1550050597036, "label": "", "is_liquidation": false, "instrument_name": "BTC-15FEB19-3250-P", "filled_amount": 0, "direction": "buy", "creation_timestamp": 1550050597036, "commission": 0, "average_price": 0, "api": true, "amount": 10 } ]`
+
+func TestGetOpenOrdersByCurrency(t *testing.T) {
+ t.Parallel()
+ var resp []OrderData
+ err := json.Unmarshal([]byte(getOpenOrdersByCurrencyResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetOpenOrdersByCurrency(context.Background(), currency.EMPTYCODE, "option", "all")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOpenOrdersByCurrency(context.Background(), currency.BTC, "option", "all")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOpenOrdersByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveOpenOrdersByCurrency(currency.EMPTYCODE, "option", "all")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveOpenOrdersByCurrency(currency.BTC, "option", "all")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetOpenOrdersByLabel(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOpenOrdersByLabel(context.Background(), currency.EMPTYCODE, "the-label")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOpenOrdersByLabel(context.Background(), currency.EURR, "the-label")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOpenOrdersByLabel(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveOpenOrdersByLabel(currency.EMPTYCODE, "the-label")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSRetrieveOpenOrdersByLabel(currency.EURR, "the-label")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetOpenOrdersByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOpenOrdersByInstrument(context.Background(), "", "all")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOpenOrdersByInstrument(context.Background(), btcPerpInstrument, "all")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOpenOrdersByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveOpenOrdersByInstrument("", "all")
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveOpenOrdersByInstrument(btcPerpInstrument, "all")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetOrderHistoryByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOrderHistoryByCurrency(context.Background(), currency.EMPTYCODE, "future", 0, 0, false, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOrderHistoryByCurrency(context.Background(), currency.BTC, "future", 0, 0, false, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOrderHistoryByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveOrderHistoryByCurrency(currency.EMPTYCODE, "future", 0, 0, false, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveOrderHistoryByCurrency(currency.BTC, "future", 0, 0, false, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetOrderHistoryByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOrderHistoryByInstrument(context.Background(), "", 0, 0, false, false)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOrderHistoryByInstrument(context.Background(), btcPerpInstrument, 0, 0, false, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOrderHistoryByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveOrderHistoryByInstrument("", 0, 0, false, false)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveOrderHistoryByInstrument(btcPerpInstrument, 0, 0, false, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetOrderMarginsByID(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOrderMarginsByID(context.Background(), []string{})
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOrderMarginsByID(context.Background(), []string{"21422175153", "21422175154"})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveOrderMarginsByID(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveOrderMarginsByID([]string{})
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveOrderMarginsByID([]string{"ETH-349280", "ETH-349279", "ETH-349278"})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetOrderState(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOrderState(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOrderState(context.Background(), "brokenid123")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrievesOrderState(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrievesOrderState("")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrievesOrderState("brokenid123")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetOrderStateByLabel(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOrderStateByLabel(context.Background(), currency.EMPTYCODE, "the-label")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetOrderStateByLabel(context.Background(), currency.EURR, "the-label")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsRetrieveOrderStateByLabel(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsRetrieveOrderStateByLabel(currency.EMPTYCODE, "the-label")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WsRetrieveOrderStateByLabel(currency.EURR, "the-label")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetTriggerOrderHistory(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetTriggerOrderHistory(context.Background(), currency.EMPTYCODE, "", "", 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetTriggerOrderHistory(context.Background(), currency.ETH, "", "", 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveTriggerOrderHistory(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveTriggerOrderHistory(currency.EMPTYCODE, "", "", 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveTriggerOrderHistory(currency.ETH, "", "", 0)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getUserTradesByCurrencyResponseJSON = `{"trades": [ { "underlying_price": 204.5, "trade_seq": 3, "trade_id": "ETH-2696060", "timestamp": 1590480363130, "tick_direction": 2, "state": "filled", "reduce_only": false, "price": 0.361, "post_only": false, "order_type": "limit", "order_id": "ETH-584827850", "matching_id": null, "mark_price": 0.364585, "liquidity": "T", "iv": 0, "instrument_name": "ETH-29MAY20-130-C", "index_price": 203.72, "fee_currency": "ETH", "fee": 0.002, "direction": "sell", "amount": 5 }, { "underlying_price": 204.82, "trade_seq": 3, "trade_id": "ETH-2696062", "timestamp": 1590480416119, "tick_direction": 0, "state": "filled", "reduce_only": false, "price": 0.015, "post_only": false, "order_type": "limit", "order_id": "ETH-584828229", "matching_id": null, "mark_price": 0.000596, "liquidity": "T", "iv": 352.91, "instrument_name": "ETH-29MAY20-140-P", "index_price": 204.06, "fee_currency": "ETH", "fee": 0.002, "direction": "buy", "amount": 5 } ], "has_more": true }`
+
+func TestGetUserTradesByCurrency(t *testing.T) {
+ t.Parallel()
+ var resp *UserTradesData
+ err := json.Unmarshal([]byte(getUserTradesByCurrencyResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetUserTradesByCurrency(context.Background(), currency.EMPTYCODE, "future", "", "", "asc", 0, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserTradesByCurrency(context.Background(), currency.ETH, "future", "", "", "asc", 0, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveUserTradesByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveUserTradesByCurrency(currency.EMPTYCODE, "future", "", "", "asc", 0, false)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveUserTradesByCurrency(currency.ETH, "future", "", "", "asc", 0, false)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetUserTradesByCurrencyAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetUserTradesByCurrencyAndTime(context.Background(), currency.EMPTYCODE, "future", "default", 5, time.Now().Add(-time.Hour*10), time.Now().Add(-time.Hour*1))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserTradesByCurrencyAndTime(context.Background(), currency.ETH, "future", "default", 5, time.Now().Add(-time.Hour*10), time.Now().Add(-time.Hour*1))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveUserTradesByCurrencyAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveUserTradesByCurrencyAndTime(currency.EMPTYCODE, "future", "default", 5, time.Now().Add(-time.Hour*4), time.Now())
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveUserTradesByCurrencyAndTime(currency.ETH, "future", "default", 5, time.Now().Add(-time.Hour*4), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetUserTradesByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetUserTradesByInstrument(context.Background(), "", "asc", 5, 10, 4, true)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserTradesByInstrument(context.Background(), btcPerpInstrument, "asc", 5, 10, 4, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsRetrieveUserTradesByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsRetrieveUserTradesByInstrument("", "asc", 5, 10, 4, true)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WsRetrieveUserTradesByInstrument(btcPerpInstrument, "asc", 5, 10, 4, true)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetUserTradesByInstrumentAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetUserTradesByInstrumentAndTime(context.Background(), "", "asc", 10, time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserTradesByInstrumentAndTime(context.Background(), btcPerpInstrument, "asc", 10, time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveUserTradesByInstrumentAndTime(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveUserTradesByInstrumentAndTime("", "asc", 10, false, time.Now().Add(-time.Hour), time.Now())
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveUserTradesByInstrumentAndTime(btcPerpInstrument, "asc", 10, false, time.Now().Add(-time.Hour), time.Now())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetUserTradesByOrder(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetUserTradesByOrder(context.Background(), "", "default")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserTradesByOrder(context.Background(), "wrongOrderID", "default")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveUserTradesByOrder(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveUserTradesByOrder("", "default")
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveUserTradesByOrder("wrongOrderID", "default")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestResetMMP(t *testing.T) {
+ t.Parallel()
+ err := d.ResetMMP(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.ResetMMP(context.Background(), currency.BTC)
+ assert.NoError(t, err)
+}
+
+func TestWSResetMMP(t *testing.T) {
+ t.Parallel()
+ err := d.WSResetMMP(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.WSResetMMP(currency.BTC)
+ assert.NoError(t, err)
+}
+func TestSendRequestForQuote(t *testing.T) {
+ t.Parallel()
+ err := d.SendRequestForQuote(context.Background(), "", 1000, order.Buy)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.SendRequestForQuote(context.Background(), d.formatFuturesTradablePair(futuresTradablePair), 1000, order.Buy)
+ assert.NoError(t, err)
+}
+
+func TestWSSendRequestForQuote(t *testing.T) {
+ t.Parallel()
+ err := d.WSSendRequestForQuote("", 1000, order.Buy)
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.WSSendRequestForQuote(d.formatFuturesTradablePair(futuresTradablePair), 1000, order.Buy)
+ assert.NoError(t, err)
+}
+func TestSetMMPConfig(t *testing.T) {
+ t.Parallel()
+ err := d.SetMMPConfig(context.Background(), currency.EMPTYCODE, kline.FiveMin, 5, 0, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.SetMMPConfig(context.Background(), currency.BTC, kline.FiveMin, 5, 0, 0)
+ assert.NoError(t, err)
+}
+
+func TestWSSetMMPConfig(t *testing.T) {
+ t.Parallel()
+ err := d.WSSetMMPConfig(currency.EMPTYCODE, kline.FiveMin, 5, 0, 0)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.WSSetMMPConfig(currency.BTC, kline.FiveMin, 5, 0, 0)
+ assert.NoError(t, err)
+}
+
+func TestGetSettlementHistoryByCurency(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetSettlementHistoryByCurency(context.Background(), currency.EMPTYCODE, "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetSettlementHistoryByCurency(context.Background(), currency.BTC, "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveSettlementHistoryByCurency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveSettlementHistoryByCurency(currency.EMPTYCODE, "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveSettlementHistoryByCurency(currency.BTC, "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getSettlementHistoryByInstrumentResponseJSON = `{"settlements": [ { "type": "settlement", "timestamp": 1550475692526, "session_profit_loss": 0.038358299, "profit_loss": -0.001783937, "position": -66, "mark_price": 121.67, "instrument_name": "ETH-22FEB19", "index_price": 119.8 } ], "continuation": "xY7T6cusbMBNpH9SNmKb94jXSBxUPojJEdCPL4YociHBUgAhWQvEP" }`
+
+func TestGetSettlementHistoryByInstrument(t *testing.T) {
+ t.Parallel()
+ var result *PrivateSettlementsHistoryData
+ err := json.Unmarshal([]byte(getSettlementHistoryByInstrumentResponseJSON), &result)
+ require.NoError(t, err)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err = d.GetSettlementHistoryByInstrument(context.Background(), btcPerpInstrument, "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveSettlementHistoryByInstrument(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveSettlementHistoryByInstrument("", "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveSettlementHistoryByInstrument(btcPerpInstrument, "settlement", "", 10, time.Now().Add(-time.Hour))
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestSubmitEdit(t *testing.T) {
+ t.Parallel()
+ _, err := d.SubmitEdit(context.Background(), &OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+ _, err = d.SubmitEdit(context.Background(), &OrderBuyAndSellParams{OrderID: "", Advanced: "", TriggerPrice: 0.001, Price: 100000, Amount: 123})
+ require.ErrorIs(t, err, errInvalidID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.SubmitEdit(context.Background(), &OrderBuyAndSellParams{OrderID: "incorrectID", Advanced: "", TriggerPrice: 0.001, Price: 100000, Amount: 123})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSSubmitEdit(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSSubmitEdit(&OrderBuyAndSellParams{})
+ require.ErrorIs(t, err, common.ErrNilPointer)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSSubmitEdit(&OrderBuyAndSellParams{
+ OrderID: "incorrectID",
+ Advanced: "",
+ TriggerPrice: 0.001,
+ Price: 100000,
+ Amount: 123,
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+// Combo Books Endpoints
+
+func TestGetComboIDS(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetComboIDs(context.Background(), currency.EMPTYCODE, "")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetComboIDs(context.Background(), currency.BTC, "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveComboIDS(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveComboIDs(currency.EMPTYCODE, "")
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ combos, err := d.WSRetrieveComboIDs(currency.BTC, "")
+ require.NoError(t, err)
+ assert.NotEmpty(t, combos)
+}
+func TestGetComboDetails(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetComboDetails(context.Background(), "")
+ require.ErrorIs(t, err, errInvalidComboID)
+
+ result, err := d.GetComboDetails(context.Background(), futureComboTradablePair.String())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveComboDetails(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveComboDetails("")
+ require.ErrorIs(t, err, errInvalidComboID)
+
+ result, err := d.WSRetrieveComboDetails(futureComboTradablePair.String())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetCombos(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetCombos(context.Background(), currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.GetCombos(context.Background(), currency.BTC)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestCreateCombo(t *testing.T) {
+ t.Parallel()
+ _, err := d.CreateCombo(context.Background(), []ComboParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ instruments, err := d.GetEnabledPairs(asset.Futures)
+ require.NoError(t, err)
+ if len(instruments) < 2 {
+ t.Skip("no enough instrument found")
+ }
+ _, err = d.CreateCombo(context.Background(), []ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Direction: "sell",
+ },
+ {
+ InstrumentName: instruments[1].String(),
+ Direction: "sell",
+ Amount: 1200,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.CreateCombo(context.Background(), []ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Amount: 123,
+ },
+ {
+ InstrumentName: instruments[1].String(),
+ Direction: "sell",
+ Amount: 1200,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidOrderSideOrDirection)
+ _, err = d.CreateCombo(context.Background(), []ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Direction: "buy",
+ Amount: 123,
+ },
+ {
+ InstrumentName: instruments[1].String(),
+ Direction: "buy",
+ Amount: 1200,
+ },
+ })
+ require.ErrorIs(t, err, errDifferentInstruments)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.CreateCombo(context.Background(), []ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Direction: "buy",
+ Amount: 123,
+ },
+ {
+ InstrumentName: instruments[0].String(),
+ Direction: "sell",
+ Amount: 1200,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSCreateCombo(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSCreateCombo([]ComboParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ instruments, err := d.GetEnabledPairs(asset.Futures)
+ require.NoError(t, err)
+ if len(instruments) < 2 {
+ t.Skip("no enough instrument found")
+ }
+ _, err = d.WSCreateCombo([]ComboParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ _, err = d.WSCreateCombo([]ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Direction: "sell",
+ },
+ {
+ InstrumentName: instruments[1].String(),
+ Direction: "sell",
+ Amount: 1200,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WSCreateCombo([]ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Amount: 123,
+ },
+ {
+ InstrumentName: instruments[1].String(),
+ Direction: "sell",
+ Amount: 1200,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidOrderSideOrDirection)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSCreateCombo([]ComboParam{
+ {
+ InstrumentName: instruments[0].String(),
+ Direction: "sell",
+ Amount: 123,
+ },
+ {
+ InstrumentName: instruments[1].String(),
+ Direction: "buy",
+ Amount: 1200,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestVerifyBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.VerifyBlockTrade(context.Background(), time.Now(), "", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingNonce)
+ _, err = d.VerifyBlockTrade(context.Background(), time.Now(), "nonce-string", "", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errInvalidTradeRole)
+ _, err = d.VerifyBlockTrade(context.Background(), time.Now(), "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ require.NotNil(t, info)
+ _, err = d.VerifyBlockTrade(context.Background(), time.Time{}, "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.ErrorIs(t, err, errZeroTimestamp)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.VerifyBlockTrade(context.Background(), time.Now(), "something", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: order.Buy.Lower(),
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSVerifyBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSVerifyBlockTrade(time.Now(), "", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingNonce)
+ _, err = d.WSVerifyBlockTrade(time.Now(), "nonce-string", "", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errInvalidTradeRole)
+ _, err = d.WSVerifyBlockTrade(time.Now(), "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ require.NotNil(t, info)
+ _, err = d.WSVerifyBlockTrade(time.Time{}, "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.ErrorIs(t, err, errZeroTimestamp)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSVerifyBlockTrade(time.Now(), "sdjkafdad", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 28000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestInvalidateBlockTradeSignature(t *testing.T) {
+ t.Parallel()
+ err := d.WsInvalidateBlockTradeSignature("")
+ require.ErrorIs(t, err, errMissingSignature)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.InvalidateBlockTradeSignature(context.Background(), "verified_signature_string")
+ assert.NoError(t, err)
+}
+
+func TestWsInvalidateBlockTradeSignature(t *testing.T) {
+ t.Parallel()
+ err := d.WsInvalidateBlockTradeSignature("")
+ require.ErrorIs(t, err, errMissingSignature)
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ err = d.WsInvalidateBlockTradeSignature("verified_signature_string")
+ assert.NoError(t, err)
+}
+func TestExecuteBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.ExecuteBlockTrade(context.Background(), time.Now(), "", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingNonce)
+ _, err = d.ExecuteBlockTrade(context.Background(), time.Now(), "nonce-string", "", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errInvalidTradeRole)
+ _, err = d.ExecuteBlockTrade(context.Background(), time.Now(), "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ require.NotNil(t, info)
+ _, err = d.ExecuteBlockTrade(context.Background(), time.Time{}, "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.ErrorIs(t, err, errZeroTimestamp)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.ExecuteBlockTrade(context.Background(), time.Now(), "something", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSExecuteBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSExecuteBlockTrade(time.Now(), "", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingNonce)
+ _, err = d.WSExecuteBlockTrade(time.Now(), "nonce-string", "", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errInvalidTradeRole)
+ _, err = d.WSExecuteBlockTrade(time.Now(), "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ require.NotNil(t, info)
+ _, err = d.WSExecuteBlockTrade(time.Time{}, "nonce-string", "maker", currency.EMPTYCODE, []BlockTradeParam{{
+ Price: 0.777 * 22000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ }})
+ require.ErrorIs(t, err, errZeroTimestamp)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSExecuteBlockTrade(time.Now(), "sdjkafdad", "maker", currency.EMPTYCODE, []BlockTradeParam{
+ {
+ Price: 0.777 * 22000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+const getUserBlocTradeResponseJSON = `[ { "trade_seq": 37, "trade_id": "92437", "timestamp": 1565089523719, "tick_direction": 3, "state": "filled", "price": 0.0001, "order_type": "limit", "order_id": "343062", "matching_id": null, "liquidity": "T", "iv": 0, "instrument_name": "BTC-9AUG19-10250-C", "index_price": 11738, "fee_currency": "BTC", "fee": 0.00025, "direction": "sell", "block_trade_id": "61", "amount": 10 }, { "trade_seq": 25350, "trade_id": "92435", "timestamp": 1565089523719, "tick_direction": 3, "state": "filled", "price": 11590, "order_type": "limit", "order_id": "343058", "matching_id": null, "liquidity": "T", "instrument_name": "BTC-PERPETUAL", "index_price": 11737.98, "fee_currency": "BTC", "fee": 0.00000164, "direction": "buy", "block_trade_id": "61", "amount": 190 } ]`
+
+func TestGetUserBlocTrade(t *testing.T) {
+ t.Parallel()
+ var resp []BlockTradeData
+ err := json.Unmarshal([]byte(getUserBlocTradeResponseJSON), &resp)
+ require.NoError(t, err)
+ _, err = d.GetUserBlockTrade(context.Background(), "")
+ require.ErrorIs(t, err, errMissingBlockTradeID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetUserBlockTrade(context.Background(), "12345567")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveUserBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveUserBlockTrade("")
+ require.ErrorIs(t, err, errMissingBlockTradeID)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveUserBlockTrade("12345567")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestGetLastBlockTradesbyCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLastBlockTradesByCurrency(context.Background(), currency.EMPTYCODE, "", "", 5)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetLastBlockTradesByCurrency(context.Background(), currency.SOL, "", "", 5)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSRetrieveLastBlockTradesByCurrency(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveLastBlockTradesByCurrency(currency.EMPTYCODE, "", "", 5)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WSRetrieveLastBlockTradesByCurrency(currency.SOL, "", "", 5)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestMovePositions(t *testing.T) {
+ t.Parallel()
+ _, err := d.MovePositions(context.Background(), currency.EMPTYCODE, 123, 345, []BlockTradeParam{})
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.MovePositions(context.Background(), currency.BTC, 0, 345, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingSubAccountID)
+ _, err = d.MovePositions(context.Background(), currency.BTC, 123, 0, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingSubAccountID)
+ _, err = d.MovePositions(context.Background(), currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "",
+ Direction: "buy",
+ Amount: 100,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.MovePositions(context.Background(), currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "BTC-PERPETUAL",
+ Direction: "buy",
+ Amount: 0,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.MovePositions(context.Background(), currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: -4,
+ InstrumentName: "BTC-PERPETUAL",
+ Direction: "buy",
+ Amount: 20,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidPrice)
+ info, err := d.GetInstrument(context.Background(), "BTC-PERPETUAL")
+ require.NoError(t, err)
+ require.NotNil(t, info)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.MovePositions(context.Background(), currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "BTC-PERPETUAL",
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWSMovePositions(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSMovePositions(currency.EMPTYCODE, 123, 345, []BlockTradeParam{})
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+ _, err = d.WSMovePositions(currency.BTC, 0, 345, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingSubAccountID)
+ _, err = d.WSMovePositions(currency.BTC, 123, 0, []BlockTradeParam{})
+ require.ErrorIs(t, err, errMissingSubAccountID)
+ _, err = d.WSMovePositions(currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "",
+ Direction: "buy",
+ Amount: 100,
+ }})
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.WSMovePositions(currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "BTC-PERPETUAL",
+ Direction: "buy",
+ Amount: 0,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WSMovePositions(currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: -4,
+ InstrumentName: "BTC-PERPETUAL",
+ Direction: "buy",
+ Amount: 20,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidPrice)
+ info, err := d.GetInstrument(context.Background(), "BTC-PERPETUAL")
+ require.NoError(t, err)
+ require.NotNil(t, info)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WSMovePositions(currency.BTC, 123, 345, []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestSimulateBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.SimulateBlockTrade(context.Background(), "", []BlockTradeParam{})
+ require.ErrorIs(t, err, errInvalidTradeRole)
+ _, err = d.SimulateBlockTrade(context.Background(), "maker", []BlockTradeParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ _, err = d.SimulateBlockTrade(context.Background(), "maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "",
+ Direction: "buy",
+ Amount: 10,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.SimulateBlockTrade(context.Background(), "maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "",
+ Amount: 10,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidOrderSideOrDirection)
+ _, err = d.SimulateBlockTrade(context.Background(), "maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "sell",
+ Amount: 0,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.SimulateBlockTrade(context.Background(), "maker", []BlockTradeParam{
+ {
+ Price: -1,
+ InstrumentName: btcPerpInstrument,
+ Direction: "sell",
+ Amount: 10,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidPrice)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ info, err := d.GetInstrument(context.Background(), "BTC-PERPETUAL")
+ require.NoError(t, err)
+ result, err := d.SimulateBlockTrade(context.Background(), "maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsSimulateBlockTrade(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsSimulateBlockTrade("", []BlockTradeParam{})
+ require.ErrorIs(t, err, errInvalidTradeRole)
+ _, err = d.WsSimulateBlockTrade("maker", []BlockTradeParam{})
+ require.ErrorIs(t, err, errNoArgumentPassed)
+ _, err = d.WsSimulateBlockTrade("maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: "",
+ Direction: "buy",
+ Amount: 10,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidInstrumentName)
+ _, err = d.WsSimulateBlockTrade("maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "",
+ Amount: 100,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidOrderSideOrDirection)
+ _, err = d.WsSimulateBlockTrade("maker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "sell",
+ Amount: 0,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidAmount)
+ _, err = d.WsSimulateBlockTrade("maker", []BlockTradeParam{
+ {
+ Price: -1,
+ InstrumentName: btcPerpInstrument,
+ Direction: "sell",
+ Amount: 100,
+ },
+ })
+ require.ErrorIs(t, err, errInvalidPrice)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ info, err := d.GetInstrument(context.Background(), "BTC-PERPETUAL")
+ require.NoError(t, err)
+ require.NotNil(t, info)
+ result, err := d.WsSimulateBlockTrade("taker", []BlockTradeParam{
+ {
+ Price: 0.777 * 25000,
+ InstrumentName: btcPerpInstrument,
+ Direction: "buy",
+ Amount: info.MinimumTradeAmount*5 + (200000 - info.MinimumTradeAmount*5) + 10,
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func setupWs() {
+ if !d.Websocket.IsEnabled() {
+ return
+ }
+ if !sharedtestvalues.AreAPICredentialsSet(d) {
+ d.Websocket.SetCanUseAuthenticatedEndpoints(false)
+ }
+ err := d.WsConnect()
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func TestGenerateDefaultSubscriptions(t *testing.T) {
+ t.Parallel()
+ result, err := d.GenerateDefaultSubscriptions()
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestFetchTicker(t *testing.T) {
+ t.Parallel()
+ var result *ticker.Price
+ var err error
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.FetchTicker(context.Background(), cp, assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestFetchOrderbook(t *testing.T) {
+ t.Parallel()
+ var result *orderbook.Base
+ var err error
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.FetchOrderbook(context.Background(), cp, assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s", assetType.String())
+ }
+}
+
+func TestUpdateAccountInfo(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.UpdateAccountInfo(context.Background(), asset.Futures)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestFetchAccountInfo(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ assetTypes := d.GetAssetTypes(true)
+ for _, assetType := range assetTypes {
+ result, err := d.FetchAccountInfo(context.Background(), assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s", assetType.String())
+ }
+}
+
+func TestGetFundingHistory(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetAccountFundingHistory(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetWithdrawalsHistory(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Empty)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetRecentTrades(t *testing.T) {
+ t.Parallel()
+ var result []trade.Data
+ var err error
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err = d.GetRecentTrades(context.Background(), cp, assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestWSRetrievePublicPortfolioMargins(t *testing.T) {
+ t.Parallel()
+ info, err := d.GetInstrument(context.Background(), btcPerpInstrument)
+ require.NoError(t, err)
+ require.NotNil(t, info)
+ result, err := d.WSRetrievePublicPortfolioMargins(currency.BTC, map[string]float64{btcPerpInstrument: info.ContractSize * 2})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestCancelAllOrders(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ var orderCancellation = &order.Cancel{
+ OrderID: "1",
+ WalletAddress: core.BitcoinDonationAddress,
+ AccountID: "1",
+ Pair: futuresTradablePair,
+ AssetType: asset.Futures,
+ }
+ var result order.CancelAllResponse
+ var err error
+ for assetType, cp := range assetTypeToPairsMap {
+ orderCancellation.AssetType = assetType
+ orderCancellation.Pair = cp
+ result, err = d.CancelAllOrders(context.Background(), orderCancellation)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGetOrderInfo(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.GetOrderInfo(context.Background(), "1234", cp, assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGetDepositAddress(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.GetDepositAddress(context.Background(), currency.BTC, "", "")
+ require.ErrorIs(t, err, common.ErrNoResponse)
+ assert.NotNil(t, result)
+}
+
+func TestWithdraw(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WithdrawCryptocurrencyFunds(context.Background(), &withdraw.Request{
+ Exchange: d.Name,
+ Amount: 1,
+ Currency: currency.BTC,
+ Description: "WITHDRAW IT ALL",
+ Crypto: withdraw.CryptoRequest{
+ Address: "0x1nv4l1d",
+ Chain: "tetheruse",
+ },
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetActiveOrders(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ var getOrdersRequest = order.MultiOrderRequest{
+ Type: order.AnyType, AssetType: asset.Futures,
+ Side: order.AnySide, Pairs: currency.Pairs{futuresTradablePair},
+ }
+
+ for assetType, cp := range assetTypeToPairsMap {
+ getOrdersRequest.Pairs = []currency.Pair{cp}
+ getOrdersRequest.AssetType = assetType
+ result, err := d.GetActiveOrders(context.Background(), &getOrdersRequest)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGetOrderHistory(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ for assetType, cp := range assetTypeToPairsMap {
+ result, err := d.GetOrderHistory(context.Background(), &order.MultiOrderRequest{
+ Type: order.AnyType, AssetType: assetType,
+ Side: order.AnySide, Pairs: []currency.Pair{cp},
+ })
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s pair %s", err, assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestGuessAssetTypeFromInstrument(t *testing.T) {
+ var assetTypeNew asset.Item
+ for _, assetType := range []asset.Item{asset.Spot, asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} {
+ availablePairs, err := d.GetEnabledPairs(assetType)
+ require.NoErrorf(t, err, "expected nil, got %v for asset type %s", err, assetType.String())
+ require.NotNilf(t, availablePairs, "Expected result not to be nil for asset type %s", assetType.String())
+
+ format, err := d.GetPairFormat(assetType, true)
+ require.NoError(t, err)
+
+ for id, cp := range availablePairs {
+ t.Run(strconv.Itoa(id), func(t *testing.T) {
+ assetTypeNew, err = guessAssetTypeFromInstrument(cp.Format(format))
+ require.Equalf(t, assetType, assetTypeNew, "expected %s, but found %s for pair string %s", assetType.String(), assetTypeNew.String(), cp.Format(format))
+ })
+ }
+ }
+
+ cp, err := currency.NewPairFromString("some_thing_else")
+ require.NoError(t, err)
+ _, err = guessAssetTypeFromInstrument(cp)
+ assert.ErrorIs(t, err, errUnsupportedInstrumentFormat)
+}
+
+func TestGetFeeByTypeOfflineTradeFee(t *testing.T) {
+ var feeBuilder = &exchange.FeeBuilder{
+ Amount: 1,
+ FeeType: exchange.CryptocurrencyTradeFee,
+ Pair: futuresTradablePair,
+ IsMaker: false,
+ PurchasePrice: 1,
+ FiatCurrency: currency.USD,
+ BankTransactionType: exchange.WireTransfer,
+ }
+ result, err := d.GetFeeByType(context.Background(), feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ if !sharedtestvalues.AreAPICredentialsSet(d) {
+ assert.Equalf(t, exchange.OfflineTradeFee, feeBuilder.FeeType, "Expected %f, received %f", exchange.OfflineTradeFee, feeBuilder.FeeType)
+ } else {
+ assert.Equalf(t, exchange.CryptocurrencyTradeFee, feeBuilder.FeeType, "Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType)
+ }
+}
+
+func TestCalculateTradingFee(t *testing.T) {
+ t.Parallel()
+ feeBuilder := &exchange.FeeBuilder{
+ FeeType: exchange.CryptocurrencyTradeFee,
+ Pair: currency.Pair{Base: currency.BTC, Quote: currency.USD, Delimiter: currency.DashDelimiter},
+ IsMaker: true,
+ Amount: 1,
+ PurchasePrice: 1000,
+ }
+ var result float64
+ result, err := calculateTradingFee(feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Equalf(t, 1e-1, result, "expected result %f, got %f", 1e-1, result)
+ // futures
+ feeBuilder.Pair, err = currency.NewPairFromString("BTC-21OCT22")
+ require.NoError(t, err)
+ result, err = calculateTradingFee(feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Equalf(t, 0.1, result, "expected 0.1 but found %f", result)
+ // options
+ feeBuilder.Pair, err = currency.NewPairFromString("SOL-21OCT22-20-C")
+ require.NoError(t, err)
+ feeBuilder.IsMaker = false
+ result, err = calculateTradingFee(feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Equalf(t, 0.3, result, "expected 0.3 but found %f", result)
+ // options
+ feeBuilder.Pair, err = currency.NewPairFromString("SOL-21OCT22-20-C,SOL-21OCT22-20-P")
+ require.NoError(t, err)
+ feeBuilder.IsMaker = true
+ _, err = calculateTradingFee(feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Equalf(t, 0.3, result, "expected 0.3 but found %f", result)
+ // option_combo
+ feeBuilder.Pair, err = currency.NewPairFromString("BTC-STRG-21OCT22-19000_21000")
+ require.NoError(t, err)
+ feeBuilder.IsMaker = false
+ result, err = calculateTradingFee(feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ // future_combo
+ feeBuilder.Pair, err = currency.NewPairFromString("SOL-FS-30DEC22_28OCT22")
+ require.NoError(t, err)
+ feeBuilder.IsMaker = false
+ result, err = calculateTradingFee(feeBuilder)
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ feeBuilder.Pair, err = currency.NewPairFromString("some_instrument_builder")
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ _, err = calculateTradingFee(feeBuilder)
+ assert.ErrorIs(t, err, errUnsupportedInstrumentFormat)
+}
+
+func TestGetTime(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetTime(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWrapperGetServerTime(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetServerTime(context.Background(), asset.Empty)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestModifyOrder(t *testing.T) {
+ t.Parallel()
+ modifyParamToErrorMap := map[*order.Modify]error{
+ {OrderID: "1234"}: order.ErrPairIsEmpty,
+ {AssetType: asset.Spot}: order.ErrPairIsEmpty,
+ {AssetType: asset.Margin, OrderID: "1234", Pair: spotTradablePair}: asset.ErrNotSupported,
+ {AssetType: asset.Futures, OrderID: "1234", Pair: futuresTradablePair}: errInvalidAmount,
+ {AssetType: asset.Futures, Pair: futuresTradablePair, Amount: 2}: order.ErrOrderIDNotSet,
+ }
+ for param, errIncoming := range modifyParamToErrorMap {
+ _, err := d.ModifyOrder(context.Background(), param)
+ require.ErrorIs(t, err, errIncoming)
+ }
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.ModifyOrder(context.Background(), &order.Modify{AssetType: asset.Futures, OrderID: "1234", Pair: futuresTradablePair, Amount: 2})
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ result, err = d.ModifyOrder(context.Background(), &order.Modify{AssetType: asset.Options, OrderID: "1234", Pair: optionsTradablePair, Amount: 2})
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestCancelOrder(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ var orderCancellation = &order.Cancel{
+ OrderID: "1",
+ WalletAddress: core.BitcoinDonationAddress,
+ AccountID: "1",
+ }
+ for assetType, cp := range assetTypeToPairsMap {
+ orderCancellation.AssetType = assetType
+ orderCancellation.Pair = cp
+ err := d.CancelOrder(context.Background(), orderCancellation)
+ require.NoError(t, err)
+ }
+}
+
+var websocketPushData = map[string]string{
+ "Announcement": `{"jsonrpc": "2.0","method": "subscription","params": {"channel": "announcements","data": { "action": "new", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "id": 1532593832021, "important": true, "publication_timestamp": 1532593832021, "title": "Example announcement" } }}`,
+ "Orderbook": `{"jsonrpc": "2.0", "method": "subscription", "params": { "channel": "book.BTC-PERPETUAL.100ms", "data": { "type": "snapshot", "timestamp": 1677589058217, "instrument_name": "BTC-PERPETUAL", "change_id": 53639437695, "bids": [ [ "new", 23461.0, 47800.0 ], [ "new", 23460.5, 37820.0 ], [ "new", 23460.0, 45720.0 ], [ "new", 23459.5, 24030.0 ], [ "new", 23459.0, 63600.0 ], [ "new", 23458.5, 60480.0 ], [ "new", 23458.0, 7960.0 ], [ "new", 23457.5, 2310.0 ], [ "new", 23457.0, 4270.0 ], [ "new", 23456.5, 44070.0 ], [ "new", 23456.0, 88690.0 ], [ "new", 23455.5, 5650.0 ], [ "new", 23455.0, 13420.0 ], [ "new", 23454.5, 116710.0 ], [ "new", 23454.0, 2010.0 ], [ "new", 23453.5, 200000.0 ], [ "new", 23452.5, 19950.0 ], [ "new", 23452.0, 39360.0 ], [ "new", 23451.5, 10000.0 ], [ "new", 23451.0, 239510.0 ], [ "new", 23450.5, 6250.0 ], [ "new", 23450.0, 40080.0 ], [ "new", 23449.5, 2000.0 ], [ "new", 23448.5, 500.0 ], [ "new", 23447.5, 179810.0 ], [ "new", 23447.0, 11000.0 ], [ "new", 23446.0, 57730.0 ], [ "new", 23445.0, 3640.0 ], [ "new", 23444.0, 17640.0 ], [ "new", 23443.5, 50000.0 ], [ "new", 23443.0, 6250.0 ], [ "new", 23441.5, 30330.0 ], [ "new", 23440.5, 76990.0 ], [ "new", 23440.0, 23910.0 ], [ "new", 23439.5, 3000.0 ], [ "new", 23439.0, 990.0 ], [ "new", 23438.0, 20760.0 ], [ "new", 23437.5, 500.0 ], [ "new", 23437.0, 84970.0 ], [ "new", 23436.5, 30040.0 ], [ "new", 23435.5, 322380.0 ], [ "new", 23434.0, 86280.0 ], [ "new", 23433.5, 187860.0 ], [ "new", 23433.0, 102360.0 ], [ "new", 23432.5, 48250.0 ], [ "new", 23432.0, 29070.0 ], [ "new", 23430.0, 119780.0 ], [ "new", 23429.0, 10.0 ], [ "new", 23428.0, 1510.0 ], [ "new", 23427.5, 2000.0 ], [ "new", 23427.0, 10.0 ], [ "new", 23426.5, 1840.0 ], [ "new", 23425.5, 2000.0 ], [ "new", 23425.0, 2250.0 ], [ "new", 23424.5, 600000.0 ], [ "new", 23424.0, 40870.0 ], [ "new", 23423.0, 117200.0 ], [ "new", 23422.0, 5000.0 ], [ "new", 23421.5, 80970.0 ], [ "new", 23420.0, 2420.0 ], [ "new", 23419.5, 200.0 ], [ "new", 23418.5, 40000.0 ], [ "new", 23415.0, 8020.0 ], [ "new", 23414.5, 57730.0 ], [ "new", 23413.5, 133250.0 ], [ "new", 23412.0, 40000.0 ], [ "new", 23410.5, 24000.0 ], [ "new", 23410.0, 80.0 ], [ "new", 23408.0, 36000.0 ], [ "new", 23407.0, 550000.0 ], [ "new", 23406.0, 30.0 ], [ "new", 23404.5, 230.0 ], [ "new", 23402.5, 57730.0 ], [ "new", 23401.0, 300010.0 ], [ "new", 23400.0, 520.0 ], [ "new", 23398.0, 28980.0 ], [ "new", 23394.5, 10.0 ], [ "new", 23391.5, 200.0 ], [ "new", 23391.0, 150000.0 ], [ "new", 23390.0, 80.0 ], [ "new", 23387.0, 403640.0 ], [ "new", 23385.5, 110.0 ], [ "new", 23385.0, 50.0 ], [ "new", 23384.5, 4690.0 ], [ "new", 23381.0, 200.0 ], [ "new", 23101.0, 9240.0 ], [ "new", 23100.5, 2320.0 ], [ "new", 23100.0, 15360.0 ], [ "new", 23096.0, 3000.0 ], [ "new", 23090.0, 90.0 ], [ "new", 23088.0, 3000.0 ], [ "new", 23087.0, 60.0 ], [ "new", 23081.5, 100.0 ], [ "new", 23080.0, 5400.0 ], [ "new", 23072.0, 3000.0 ], [ "new", 23070.0, 80.0 ], [ "new", 23064.0, 3000.0 ], [ "new", 23062.0, 3270.0 ], [ "new", 23060.0, 80.0 ], [ "new", 23056.0, 98000.0 ], [ "new", 23053.0, 3500.0 ], [ "new", 23050.5, 2370.0 ], [ "new", 23050.0, 32510.0 ], [ "new", 23048.0, 3000.0 ], [ "new", 23040.0, 3080.0 ], [ "new", 23038.0, 1000.0 ], [ "new", 23032.0, 5310.0 ], [ "new", 23030.0, 100.0 ], [ "new", 23024.0, 29000.0 ], [ "new", 23021.0, 2080.0 ], [ "new", 23020.0, 80.0 ], [ "new", 23016.0, 4150.0 ], [ "new", 23010.0, 80.0 ], [ "new", 23008.0, 3000.0 ], [ "new", 23005.0, 80.0 ], [ "new", 23004.5, 79200.0 ], [ "new", 23002.0, 20470.0 ], [ "new", 23001.0, 1000.0 ], [ "new", 23000.0, 8940.0 ], [ "new", 22992.0, 3000.0 ], [ "new", 22990.0, 2080.0 ], [ "new", 22984.0, 3000.0 ], [ "new", 22980.5, 2320.0 ], [ "new", 22980.0, 80.0 ], [ "new", 22976.0, 3000.0 ], [ "new", 22975.0, 52000.0 ], [ "new", 22971.0, 3600.0 ], [ "new", 22970.0, 2400.0 ], [ "new", 22968.0, 3000.0 ], [ "new", 22965.0, 270.0 ], [ "new", 22960.0, 3080.0 ], [ "new", 22956.0, 1000.0 ], [ "new", 22952.0, 3000.0 ], [ "new", 22951.0, 60.0 ], [ "new", 22950.0, 40200.0 ], [ "new", 22949.0, 1500.0 ], [ "new", 22944.0, 3000.0 ], [ "new", 22936.0, 3000.0 ], [ "new", 22934.0, 3000.0 ], [ "new", 22928.0, 3000.0 ], [ "new", 22925.0, 2370.0 ], [ "new", 22922.0, 80.0 ], [ "new", 22920.0, 3000.0 ], [ "new", 22916.0, 1150.0 ], [ "new", 22912.0, 3000.0 ], [ "new", 22904.5, 220.0 ], [ "new", 22904.0, 3000.0 ], [ "new", 22900.0, 273290.0 ], [ "new", 22896.0, 3000.0 ], [ "new", 22889.5, 100.0 ], [ "new", 22888.0, 7580.0 ], [ "new", 22880.0, 683400.0 ], [ "new", 22875.0, 400.0 ], [ "new", 22872.0, 3000.0 ], [ "new", 22870.0, 100.0 ], [ "new", 22864.0, 3000.0 ], [ "new", 22860.0, 2320.0 ], [ "new", 22856.0, 3000.0 ], [ "new", 22854.0, 10.0 ], [ "new", 22853.0, 500.0 ], [ "new", 22850.0, 1020.0 ], [ "new", 22848.0, 3000.0 ], [ "new", 22844.0, 25730.0 ], [ "new", 22840.0, 3000.0 ], [ "new", 22834.0, 3000.0 ], [ "new", 22832.0, 3000.0 ], [ "new", 22831.0, 200.0 ], [ "new", 22827.0, 40120.0 ], [ "new", 22824.0, 3000.0 ], [ "new", 22816.0, 4140.0 ], [ "new", 22808.0, 3000.0 ], [ "new", 22804.5, 220.0 ], [ "new", 22802.0, 50.0 ], [ "new", 22801.0, 1150.0 ], [ "new", 22800.0, 14050.0 ], [ "new", 22797.0, 10.0 ], [ "new", 22792.0, 3000.0 ], [ "new", 22789.0, 3000.0 ], [ "new", 22787.5, 5000.0 ], [ "new", 22784.0, 3000.0 ], [ "new", 22776.0, 3000.0 ], [ "new", 22775.0, 10000.0 ], [ "new", 22770.0, 200.0 ], [ "new", 22768.0, 14380.0 ], [ "new", 22760.0, 3000.0 ], [ "new", 22756.5, 2370.0 ], [ "new", 22752.0, 3000.0 ], [ "new", 22751.0, 47780.0 ], [ "new", 22750.0, 59970.0 ], [ "new", 22749.0, 50.0 ], [ "new", 22744.0, 3000.0 ], [ "new", 22736.0, 3000.0 ], [ "new", 22728.0, 3000.0 ], [ "new", 22726.0, 2320.0 ], [ "new", 22725.0, 20000.0 ], [ "new", 22720.0, 3000.0 ], [ "new", 22713.5, 250.0 ], [ "new", 22712.0, 3000.0 ], [ "new", 22709.0, 25000.0 ], [ "new", 22704.5, 220.0 ], [ "new", 22704.0, 3000.0 ], [ "new", 22702.0, 50.0 ], [ "new", 22700.0, 10230.0 ], [ "new", 22697.5, 10.0 ], [ "new", 22696.0, 3000.0 ], [ "new", 22688.0, 3000.0 ], [ "new", 22684.0, 10.0 ], [ "new", 22680.0, 3000.0 ], [ "new", 22672.0, 3000.0 ], [ "new", 22667.0, 2270.0 ], [ "new", 22664.0, 3000.0 ], [ "new", 22662.5, 2320.0 ], [ "new", 22657.5, 2340.0 ], [ "new", 22656.0, 3000.0 ], [ "new", 22655.0, 50.0 ], [ "new", 22653.0, 500.0 ], [ "new", 22650.0, 360120.0 ], [ "new", 22648.0, 3000.0 ], [ "new", 22640.0, 5320.0 ], [ "new", 22635.5, 2350.0 ], [ "new", 22632.0, 3000.0 ], [ "new", 22628.5, 2000.0 ], [ "new", 22626.5, 2350.0 ], [ "new", 22625.0, 400.0 ], [ "new", 22624.0, 3000.0 ], [ "new", 22616.0, 3000.0 ], [ "new", 22608.0, 3000.0 ], [ "new", 22604.5, 220.0 ], [ "new", 22601.0, 22600.0 ], [ "new", 22600.0, 696120.0 ], [ "new", 22598.5, 2320.0 ], [ "new", 22592.0, 3000.0 ], [ "new", 22584.0, 3000.0 ], [ "new", 22576.0, 3000.0 ], [ "new", 22568.0, 3000.0 ], [ "new", 22560.0, 25560.0 ], [ "new", 22552.5, 20.0 ], [ "new", 22550.0, 35760.0 ], [ "new", 22533.0, 2320.0 ], [ "new", 22530.0, 2320.0 ], [ "new", 22520.0, 1000.0 ], [ "new", 22505.0, 20000.0 ], [ "new", 22504.5, 220.0 ], [ "new", 22501.0, 45000.0 ], [ "new", 22500.0, 27460.0 ], [ "new", 22497.5, 1500.0 ], [ "new", 22485.0, 810.0 ], [ "new", 22481.0, 300.0 ], [ "new", 22465.5, 2320.0 ], [ "new", 22456.0, 2350.0 ], [ "new", 22453.0, 500.0 ], [ "new", 22450.0, 25000.0 ], [ "new", 22433.0, 141000.0 ], [ "new", 22431.0, 1940.0 ], [ "new", 22420.0, 2320.0 ], [ "new", 22419.5, 1000000.0 ], [ "new", 22400.0, 14280.0 ], [ "new", 22388.5, 30.0 ], [ "new", 22381.0, 100.0 ] ] } } }`,
+ "Orderbook Update": `{"params" : {"data" : {"type" : "snapshot","timestamp" : 1554373962454,"instrument_name" : "BTC-PERPETUAL","change_id" : 297217,"bids" : [["new",5042.34,30],["new",5041.94,20]],"asks" : [["new",5042.64,40],["new",5043.3,40]]},"channel" : "book.BTC-PERPETUAL.100ms"}, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Candlestick": `{"params" : {"data" : {"volume" : 0.05219351,"tick" : 1573645080000,"open" : 8869.79,"low" : 8788.25,"high" : 8870.31,"cost" : 460,"close" : 8791.25},"channel" : "chart.trades.BTC-PERPETUAL.1"}, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Index Price": `{"params" : {"data" : {"timestamp" : 1550588002899,"price" : 3937.89,"index_name" : "btc_usd"},"channel" : "deribit_price_index.btc_usd" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Price Ranking": `{"params" : {"data" :[{"weight" : 14.29,"timestamp" : 1573202284040,"price" : 9109.35,"original_price" : 9109.35,"identifier" : "bitfinex","enabled" : true},{"weight" : 14.29,"timestamp" : 1573202284055,"price" : 9084.83,"original_price" : 9084.83,"identifier" : "bitstamp","enabled" : true }, { "weight" : 14.29,"timestamp" : 1573202283191,"price" : 9079.91,"original_price" : 9079.91,"identifier" : "bittrex","enabled" : true }, { "weight" : 14.29,"timestamp" : 1573202284094,"price" : 9085.81,"original_price" : 9085.81,"identifier" : "coinbase","enabled" : true }, { "weight" : 14.29,"timestamp" : 1573202283881,"price" : 9086.27,"original_price" : 9086.27,"identifier" : "gemini","enabled" : true }, { "weight" : 14.29,"timestamp" : 1573202283420,"price" : 9088.38,"original_price" : 9088.38,"identifier" : "itbit","enabled" : true }, { "weight" : 14.29,"timestamp" : 1573202283459,"price" : 9083.6,"original_price" : 9083.6,"identifier" : "kraken","enabled" : true }, { "weight" : 0,"timestamp" : 0,"price" : null,"original_price" : null,"identifier" : "lmax","enabled" : false } ], "channel" : "deribit_price_ranking.btc_usd" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Price Statistics": `{"params" : {"data" : {"low24h" : 58012.08,"index_name" : "btc_usd","high_volatility" : false,"high24h" : 59311.42,"change24h" : 1009.61},"channel" : "deribit_price_statistics.btc_usd"}, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Volatility Index": `{"params" : {"data" : {"volatility" : 129.36,"timestamp" : 1619777946007,"index_name" : "btc_usd","estimated_delivery" : 129.36},"channel" : "deribit_volatility_index.btc_usd" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Estimated Expiration Price": `{"params" : {"data" : {"seconds" : 180929,"price" : 3939.73,"is_estimated" : false},"channel" : "estimated_expiration_price.btc_usd" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Incremental Ticker": `{"jsonrpc": "2.0", "method": "subscription", "params": { "channel": "incremental_ticker.BTC-PERPETUAL", "data": { "type": "snapshot", "timestamp": 1677592580023, "stats": { "volume_usd": 224579520.0, "volume": 9581.70741368, "price_change": -1.2945, "low": 23123.5, "high": 23900.0 }, "state": "open", "settlement_price": 23240.71, "open_interest": 333091400, "min_price": 23057.4, "max_price": 23759.65, "mark_price": 23408.41, "last_price": 23409.0, "interest_value": 0.0, "instrument_name": "BTC-PERPETUAL", "index_price": 23406.85, "funding_8h": 0.0, "estimated_delivery_price": 23406.85, "current_funding": 0.0, "best_bid_price": 23408.5, "best_bid_amount": 53270.0, "best_ask_price": 23409.0, "best_ask_amount": 46990.0 } } }`,
+ "Instrument State": `{"params" : {"data" : {"timestamp" : 1553080940000,"state" : "created","instrument_name" : "BTC-22MAR19"},"channel" : "instrument.state.any.any"}, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Currency Trades": `{"params":{"data":[{"trade_seq":2,"trade_id" : "48079289","timestamp" : 1590484589306,"tick_direction" : 2,"price" : 0.0075,"mark_price" : 0.01062686,"iv" : 47.58,"instrument_name" : "BTC-27MAY20-9000-C","index_price" : 8956.17,"direction" : "sell","amount" : 3}],"channel" : "trades.option.BTC.raw"},"method":"subscription","jsonrpc":"2.0"}`,
+ "Change Updates": `{"params" : {"data" : {"trades" : [{"trade_seq" : 866638,"trade_id" : "1430914","timestamp" : 1605780344032,"tick_direction" : 1,"state" : "filled","self_trade" : false,"reduce_only" : false,"profit_loss" : 0.00004898,"price" : 17391,"post_only" : false,"order_type" : "market","order_id" : "3398016","matching_id" : null,"mark_price" : 17391,"liquidity" : "T","instrument_name" : "BTC-PERPETUAL","index_price" : 17501.88,"fee_currency" : "BTC","fee" : 1.6e-7,"direction" : "sell","amount" : 10 } ],"positions" : [ { "total_profit_loss" : 1.69711368, "size_currency" : 10.646886321, "size" : 185160, "settlement_price" : 16025.83, "realized_profit_loss" : 0.012454598, "realized_funding" : 0.01235663, "open_orders_margin" : 0, "mark_price" : 17391, "maintenance_margin" : 0.234575865, "leverage" : 33, "kind" : "future", "interest_value" : 1.7362511643080387, "instrument_name" : "BTC-PERPETUAL", "initial_margin" : 0.319750953, "index_price" : 17501.88, "floating_profit_loss" : 0.906961435, "direction" : "buy", "delta" : 10.646886321, "average_price" : 15000 } ],"orders" : [ { "web" : true, "time_in_force" : "good_til_cancelled", "replaced" : false, "reduce_only" : false, "profit_loss" : 0.00009166, "price" : 15665.5, "post_only" : false, "order_type" : "market", "order_state" : "filled", "order_id" : "3398016", "max_show" : 10, "last_update_timestamp" : 1605780344032, "label" : "", "is_liquidation" : false, "instrument_name" : "BTC-PERPETUAL", "filled_amount" : 10, "direction" : "sell", "creation_timestamp" : 1605780344032, "commission" : 1.6e-7, "average_price" : 17391, "api" : false, "amount" : 10}],"instrument_name" : "BTC-PERPETUAL" }, "channel" : "user.changes.BTC-PERPETUAL.raw" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Currency Changes Updates": `{"params" : {"data" : {"trades" : [{"trade_seq" : 866638,"trade_id" : "1430914","timestamp" : 1605780344032,"tick_direction" : 1,"state" : "filled","self_trade" : false,"reduce_only" : false,"profit_loss" : 0.00004898,"price" : 17391,"post_only" : false,"order_type" : "market","order_id" : "3398016","matching_id" : null,"mark_price" : 17391,"liquidity" : "T","instrument_name" : "BTC-PERPETUAL","index_price" : 17501.88,"fee_currency" : "BTC","fee" : 1.6e-7,"direction" : "sell","amount" : 10 } ],"positions" : [ { "total_profit_loss" : 1.69711368, "size_currency" : 10.646886321, "size" : 185160, "settlement_price" : 16025.83, "realized_profit_loss" : 0.012454598, "realized_funding" : 0.01235663, "open_orders_margin" : 0, "mark_price" : 17391, "maintenance_margin" : 0.234575865, "leverage" : 33, "kind" : "future", "interest_value" : 1.7362511643080387, "instrument_name" : "BTC-PERPETUAL", "initial_margin" : 0.319750953, "index_price" : 17501.88, "floating_profit_loss" : 0.906961435, "direction" : "buy", "delta" : 10.646886321, "average_price" : 15000 } ],"orders" : [ { "web" : true, "time_in_force" : "good_til_cancelled", "replaced" : false, "reduce_only" : false, "profit_loss" : 0.00009166, "price" : 15665.5, "post_only" : false, "order_type" : "market", "order_state" : "filled", "order_id" : "3398016", "max_show" : 10, "last_update_timestamp" : 1605780344032, "label" : "", "is_liquidation" : false, "instrument_name" : "BTC-PERPETUAL", "filled_amount" : 10, "direction" : "sell", "creation_timestamp" : 1605780344032, "commission" : 1.6e-7, "average_price" : 17391, "api" : false, "amount" : 10 } ],"instrument_name" : "BTC-PERPETUAL" }, "channel" : "user.changes.future.BTC.raw" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Orders Raw Instrument": `{"params" : {"data" : {"time_in_force" : "good_til_cancelled","replaced" : false,"reduce_only" : false,"profit_loss" : 0,"price" : 10502.52,"post_only" : false,"original_order_type" : "market","order_type" : "limit","order_state" : "open","order_id" : "5","max_show" : 200,"last_update_timestamp" : 1581507423789,"label" : "","is_liquidation" : false,"instrument_name" : "BTC-PERPETUAL","filled_amount" : 0,"direction" : "buy","creation_timestamp" : 1581507423789,"commission" : 0,"average_price" : 0,"api" : false,"amount" : 200 }, "channel" : "user.orders.BTC-PERPETUAL.raw" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Orders By Instrument WithInterval": `{"params" : {"data" : [{"time_in_force" : "good_til_cancelled","replaced" : false,"reduce_only" : false,"profit_loss" : 0,"price" : 10460.43,"post_only" : false,"original_order_type" : "market","order_type" : "limit","order_state" : "open","order_id" : "4","max_show" : 200,"last_update_timestamp" : 1581507159533,"label" : "","is_liquidation" : false,"instrument_name" : "BTC-PERPETUAL","filled_amount" : 0,"direction" : "buy","creation_timestamp" : 1581507159533,"commission" : 0,"average_price" : 0,"api" : false,"amount" : 200 } ], "channel" : "user.orders.BTC-PERPETUAL.100ms" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Order By Currency Raw": `{"params" : {"data" : {"time_in_force" : "good_til_cancelled","replaced" : false,"reduce_only" : false,"profit_loss" : 0,"price" : 10542.68,"post_only" : false,"original_order_type" : "market","order_type" : "limit","order_state" : "open","order_id" : "6","max_show" : 200,"last_update_timestamp" : 1581507583024,"label" : "","is_liquidation" : false,"instrument_name" : "BTC-PERPETUAL","filled_amount" : 0,"direction" : "buy","creation_timestamp" : 1581507583024,"commission" : 0,"average_price" : 0,"api" : false,"amount" : 200 }, "channel" : "user.orders.any.any.raw" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Order By Currency WithInterval": `{"params" : {"data" : [{"time_in_force" : "good_til_cancelled","reduce_only" : false,"profit_loss" : 0,"price" : 3928.5,"post_only" : false,"order_type" : "limit","order_state" : "open","order_id" : "476137","max_show" : 120,"last_update_timestamp" : 1550826337209,"label" : "","is_liquidation" : false,"instrument_name" : "BTC-PERPETUAL","filled_amount" : 0,"direction" : "buy","creation_timestamp" : 1550826337209,"commission" : 0,"average_price" : 0,"api" : false,"amount" : 120 } ], "channel" : "user.orders.future.BTC.100ms" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Portfolio": `{"params" : {"data" : {"total_pl" : 0.00000425,"session_upl" : 0.00000425,"session_rpl" : -2e-8,"projected_maintenance_margin" : 0.00009141,"projected_initial_margin" : 0.00012542,"projected_delta_total" : 0.0043,"portfolio_margining_enabled" : false,"options_vega" : 0,"options_value" : 0,"options_theta" : 0,"options_session_upl" : 0,"options_session_rpl" : 0,"options_pl" : 0,"options_gamma" : 0,"options_delta" : 0,"margin_balance" : 0.2340038,"maintenance_margin" : 0.00009141,"initial_margin" : 0.00012542,"futures_session_upl" : 0.00000425,"futures_session_rpl" : -2e-8,"futures_pl" : 0.00000425,"estimated_liquidation_ratio" : 0.01822795,"equity" : 0.2340038,"delta_total" : 0.0043,"currency" : "BTC","balance" : 0.23399957,"available_withdrawal_funds" : 0.23387415,"available_funds" : 0.23387838 }, "channel" : "user.portfolio.btc" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Trades": `{"params" : {"data" : [{"trade_seq" :30289432,"trade_id":"48079254","timestamp":1590484156350,"tick_direction" : 0,"state" : "filled","self_trade" : false,"reduce_only" : false,"price" : 8954,"post_only" : false,"order_type" : "market","order_id" : "4008965646","matching_id" : null,"mark_price" : 8952.86,"liquidity" : "T","instrument_name" : "BTC-PERPETUAL","index_price" : 8956.73,"fee_currency" : "BTC","fee" : 0.00000168,"direction" : "sell","amount" : 20 }, { "trade_seq" : 30289433,"trade_id" : "48079255","timestamp" : 1590484156350,"tick_direction" : 1,"state" : "filled","self_trade" : false,"reduce_only" : false,"price" : 8954,"post_only" : false,"order_type" : "market","order_id" : "4008965646","matching_id" : null,"mark_price" : 8952.86,"liquidity" : "T","instrument_name" : "BTC-PERPETUAL","index_price" : 8956.73,"fee_currency" : "BTC","fee" : 0.00000168,"direction" : "sell","amount" : 20 }],"channel" : "user.trades.BTC-PERPETUAL.raw" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "User Trades With Currency": `{"params" : {"data" : [{"trade_seq" :74405, "trade_id":"48079262","timestamp":1590484255886,"tick_direction" : 2,"state" : "filled","self_trade" : false,"reduce_only" : false,"price" : 8947,"post_only" : false,"order_type" : "limit","order_id" : "4008978075","matching_id" : null,"mark_price" : 8970.03,"liquidity" : "T","instrument_name" : "BTC-25SEP20","index_price" : 8953.53,"fee_currency" : "BTC","fee" : 0.00049961,"direction" : "sell","amount" : 8940 } ], "channel" : "user.trades.future.BTC.100ms" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Mark Price Options": `{"params" : {"data" : [{"timestamp" : 1622470378005,"mark_price" : 0.0333,"iv" : 0.9,"instrument_name" : "BTC-2JUN21-37000-P"},{"timestamp" : 1622470378005,"mark_price" : 0.117,"iv" : 0.9,"instrument_name" : "BTC-4JUN21-40500-P"},{"timestamp" : 1622470378005,"mark_price" : 0.0177,"iv" : 0.9,"instrument_name" : "BTC-4JUN21-38250-C"},{"timestamp" : 1622470378005,"mark_price" : 0.0098,"iv" : 0.9,"instrument_name" : "BTC-1JUN21-37000-C" }, { "timestamp" : 1622470378005,"mark_price" : 0.0371,"iv" : 0.9,"instrument_name" : "BTC-4JUN21-36500-P" } ], "channel" : "markprice.options.btc_usd" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Platform State": `{"params" : {"data" : {"allow_unauthenticated_public_requests" : true},"channel" : "platform_state.public_methods_state" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Quote Ticker": `{"params" : {"data" : {"timestamp" : 1550658624149,"instrument_name" : "BTC-PERPETUAL","best_bid_price" : 3914.97,"best_bid_amount" : 40,"best_ask_price" : 3996.61,"best_ask_amount" : 50},"channel" : "quote.BTC-PERPETUAL" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Rfq": `{"params" : {"data" : {"state" : true,"side" : null,"last_rfq_tstamp" : 1634816143836,"instrument_name" : "BTC-PERPETUAL","amount" : null },"channel" : "rfq.btc" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+ "Instrument Trades": `{"params":{"data":[{"trade_seq":30289442,"trade_id" : "48079269","timestamp" : 1590484512188,"tick_direction" : 2,"price" : 8950,"mark_price" : 8948.9,"instrument_name" : "BTC-PERPETUAL","index_price" : 8955.88,"direction" : "sell","amount" : 10}],"channel" : "trades.BTC-PERPETUAL.raw"},"method":"subscription","jsonrpc":"2.0"}`,
+ "Instruments Ticker": `{"params" : {"data" : {"timestamp" : 1623060194301,"stats" : {"volume_usd" : 284061480,"volume" : 7871.02139035,"price_change" : 0.7229,"low" : 35213.5,"high" : 36824.5},"state" : "open","settlement_price" : 36169.49,"open_interest" : 502097590,"min_price" : 35898.37,"max_price" : 36991.72,"mark_price" : 36446.51,"last_price" : 36457.5,"interest_value" : 1.7362511643080387,"instrument_name" : "BTC-PERPETUAL","index_price" : 36441.64,"funding_8h" : 0.0000211,"estimated_delivery_price" : 36441.64,"current_funding" : 0,"best_bid_price" : 36442.5,"best_bid_amount" : 5000,"best_ask_price" : 36443,"best_ask_amount" : 100 }, "channel" : "ticker.BTC-PERPETUAL.raw" }, "method" : "subscription", "jsonrpc" : "2.0" }`,
+}
+
+func TestProcessPushData(t *testing.T) {
+ t.Parallel()
+ for x := range websocketPushData {
+ err := d.wsHandleData([]byte(websocketPushData[x]))
+ require.NoErrorf(t, err, "%s: Received unexpected error for", x)
+ }
+}
+
+func TestFormatFuturesTradablePair(t *testing.T) {
+ t.Parallel()
+ futuresInstrumentsOutputList := map[currency.Pair]string{
+ {Delimiter: currency.DashDelimiter, Base: currency.BTC, Quote: currency.NewCode("PERPETUAL")}: "BTC-PERPETUAL",
+ {Delimiter: currency.DashDelimiter, Base: currency.AVAX, Quote: currency.NewCode("USDC-PERPETUAL")}: "AVAX_USDC-PERPETUAL",
+ {Delimiter: currency.DashDelimiter, Base: currency.ETH, Quote: currency.NewCode("30DEC22")}: "ETH-30DEC22",
+ {Delimiter: currency.DashDelimiter, Base: currency.SOL, Quote: currency.NewCode("30DEC22")}: "SOL-30DEC22",
+ {Delimiter: currency.DashDelimiter, Base: currency.NewCode("BTCDVOL"), Quote: currency.NewCode("USDC-28JUN23")}: "BTCDVOL_USDC-28JUN23",
+ }
+ for pair, instrumentID := range futuresInstrumentsOutputList {
+ instrument := d.formatFuturesTradablePair(pair)
+ require.Equal(t, instrumentID, instrument)
+ }
+}
+
+func TestWSRetrieveCombos(t *testing.T) {
+ t.Parallel()
+ _, err := d.WSRetrieveCombos(currency.EMPTYCODE)
+ require.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty)
+
+ result, err := d.WSRetrieveCombos(futureComboTradablePair.Base)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func instantiateTradablePairs() {
+ if err := d.UpdateTradablePairs(context.Background(), true); err != nil {
+ log.Fatalf("Failed to update tradable pairs. Error: %v", err)
+ }
+
+ handleError := func(err error, msg string) {
+ if err != nil {
+ log.Fatalf("%s. Error: %v", msg, err)
+ }
+ }
+
+ updateTradablePair := func(assetType asset.Item, tradablePair *currency.Pair) {
+ if d.CurrencyPairs.IsAssetEnabled(assetType) == nil {
+ pairs, err := d.GetEnabledPairs(assetType)
+ handleError(err, fmt.Sprintf("Failed to get enabled pairs for asset type %v", assetType))
+
+ if len(pairs) == 0 {
+ handleError(currency.ErrCurrencyPairsEmpty, fmt.Sprintf("No enabled pairs for asset type %v", assetType))
+ }
+
+ if assetType == asset.Options {
+ *tradablePair, err = d.FormatExchangeCurrency(pairs[0], assetType)
+ handleError(err, "Failed to format exchange currency for options pair")
+ } else {
+ *tradablePair = pairs[0]
+ }
+ }
+ }
+ updateTradablePair(asset.Options, &optionsTradablePair)
+ updateTradablePair(asset.OptionCombo, &optionComboTradablePair)
+ updateTradablePair(asset.FutureCombo, &futureComboTradablePair)
+}
+
+func TestGetLatestFundingRates(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
+ Asset: asset.USDTMarginedFutures,
+ Pair: currency.NewPair(currency.BTC, currency.USDT),
+ IncludePredictedRate: true,
+ })
+ require.ErrorIs(t, err, asset.ErrNotSupported)
+ result, err := d.GetLatestFundingRates(context.Background(), &fundingrate.LatestRateRequest{
+ Asset: asset.Futures,
+ Pair: futuresTradablePair,
+ })
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestUpdateOrderExecutionLimits(t *testing.T) {
+ t.Parallel()
+ err := d.UpdateOrderExecutionLimits(context.Background(), asset.Spot)
+ require.NoErrorf(t, err, "Error fetching %s pairs for test: %v", asset.Spot, err)
+ instrumentInfo, err := d.GetInstruments(context.Background(), currency.BTC, d.GetAssetKind(asset.Spot), false)
+ require.NoError(t, err)
+ require.NotEmpty(t, instrumentInfo, "instrument information must not be empty")
+ limits, err := d.GetOrderExecutionLimits(asset.Spot, spotTradablePair)
+ require.NoErrorf(t, err, "Asset: %s Pair: %s Err: %v", asset.Spot, spotTradablePair, err)
+ var instrumentDetail *InstrumentData
+ for a := range instrumentInfo {
+ if instrumentInfo[a].InstrumentName == spotTradablePair.String() {
+ instrumentDetail = &instrumentInfo[a]
+ break
+ }
+ }
+ require.NotNil(t, instrumentDetail, "instrument required to be found")
+ require.Equalf(t, instrumentDetail.TickSize, limits.PriceStepIncrementSize, "Asset: %s Pair: %s Expected: %f Got: %f", asset.Spot, spotTradablePair, instrumentDetail.TickSize, limits.MinimumBaseAmount)
+ assert.Equalf(t, instrumentDetail.MinimumTradeAmount, limits.MinimumBaseAmount, "Pair: %s Expected: %f Got: %f", spotTradablePair, instrumentDetail.MinimumTradeAmount, limits.MinimumBaseAmount)
+}
+
+func TestGetLockedStatus(t *testing.T) {
+ t.Parallel()
+ result, err := d.GetLockedStatus(context.Background())
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestSayHello(t *testing.T) {
+ t.Parallel()
+ result, err := d.SayHello("Thrasher", "")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsRetrieveCancelOnDisconnect(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WsRetrieveCancelOnDisconnect("connection")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsDisableCancelOnDisconnect(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WsDisableCancelOnDisconnect("connection")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestEnableCancelOnDisconnect(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.EnableCancelOnDisconnect(context.Background(), "account")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsEnableCancelOnDisconnect(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ result, err := d.WsEnableCancelOnDisconnect("connection")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestLogout(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateRealOrders)
+ err := d.WsLogout(true)
+ assert.NoError(t, err)
+}
+func TestExchangeToken(t *testing.T) {
+ t.Parallel()
+ _, err := d.ExchangeToken(context.Background(), "", 1234)
+ require.ErrorIs(t, err, errRefreshTokenRequired)
+ _, err = d.ExchangeToken(context.Background(), "1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", 0)
+ require.ErrorIs(t, err, errSubjectIDRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.ExchangeToken(context.Background(), "1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", 1234)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsExchangeToken(t *testing.T) {
+ t.Parallel()
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ result, err := d.WsExchangeToken("1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", 1234)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+func TestForkToken(t *testing.T) {
+ t.Parallel()
+ _, err := d.ForkToken(context.Background(), "", "Sami")
+ require.ErrorIs(t, err, errRefreshTokenRequired)
+ _, err = d.ForkToken(context.Background(), "1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", "")
+ require.ErrorIs(t, err, errSessionNameRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.ForkToken(context.Background(), "1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", "Sami")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestWsForkToken(t *testing.T) {
+ t.Parallel()
+ _, err := d.WsForkToken("", "Sami")
+ require.ErrorIs(t, err, errRefreshTokenRequired)
+ _, err = d.WsForkToken("1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", "")
+ require.ErrorIs(t, err, errSessionNameRequired)
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d, canManipulateAPIEndpoints)
+ result, err := d.WsForkToken("1568800656974.1CWcuzUS.MGy49NK4hpTwvR1OYWfpqMEkH4T4oDg4tNIcrM7KdeyxXRcSFqiGzA_D4Cn7mqWocHmlS89FFmUYcmaN2H7lNKKTnhRg5EtrzsFCCiuyN0Wv9y-LbGLV3-Ojv_kbD50FoScQ8BDXS5b_w6Ir1MqEdQ3qFZ3MLcvlPiIgG2BqyJX3ybYnVpIlrVrrdYD1-lkjLcjxOBNJvvUKNUAzkQ", "Sami")
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetFuturesContractDetails(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetFuturesContractDetails(context.Background(), asset.Binary)
+ require.ErrorIs(t, err, futures.ErrNotFuturesAsset)
+
+ result, err := d.GetFuturesContractDetails(context.Background(), asset.Futures)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetFuturesPositionSummary(t *testing.T) {
+ t.Parallel()
+ paramToErrorMap := map[*futures.PositionSummaryRequest]error{
+ nil: common.ErrNilPointer,
+ {}: futures.ErrNotPerpetualFuture,
+ {Asset: asset.Futures}: currency.ErrCurrencyPairEmpty,
+ }
+
+ for param, errIncoming := range paramToErrorMap {
+ _, err := d.GetFuturesPositionSummary(context.Background(), param)
+ require.ErrorIs(t, err, errIncoming)
+ }
+
+ sharedtestvalues.SkipTestIfCredentialsUnset(t, d)
+ req := &futures.PositionSummaryRequest{
+ Asset: asset.Futures,
+ Pair: currency.NewPair(currency.BTC, currency.NewCode("PERPETUAL")),
+ }
+ result, err := d.GetFuturesPositionSummary(context.Background(), req)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestGetOpenInterest(t *testing.T) {
+ t.Parallel()
+ _, err := d.GetOpenInterest(context.Background(), key.PairAsset{
+ Base: currency.SOL.Item,
+ Quote: currency.USDC.Item,
+ Asset: asset.Spot,
+ })
+ require.ErrorIs(t, err, asset.ErrNotSupported)
+
+ _, err = d.GetOpenInterest(context.Background(), key.PairAsset{
+ Base: currency.BTC.Item,
+ Quote: optionsTradablePair.Quote.Item,
+ Asset: asset.Options,
+ })
+ require.True(t, err == nil || errors.Is(err, currency.ErrCurrencyNotFound))
+
+ var result []futures.OpenInterest
+ assetTypeToPairs := getAssetToPairMap(asset.Futures & asset.FutureCombo)
+ for assetType, cp := range assetTypeToPairs {
+ result, err = d.GetOpenInterest(context.Background(), key.PairAsset{
+ Base: cp.Base.Item,
+ Quote: cp.Quote.Item,
+ Asset: assetType,
+ })
+ require.NoErrorf(t, err, "expected nil, got %s for asset type %s pair %s", assetType.String(), cp.String())
+ require.NotNilf(t, result, "Expected result not to be nil for asset type %s pair %s", assetType.String(), cp.String())
+ }
+}
+
+func TestIsPerpetualFutureCurrency(t *testing.T) {
+ t.Parallel()
+ assetPairToErrorMap := map[asset.Item][]struct {
+ Pair currency.Pair
+ Error error
+ Response bool
+ }{
+ asset.Spot: {
+ {Pair: currency.EMPTYPAIR, Error: futures.ErrNotPerpetualFuture},
+ },
+ asset.Futures: {
+ {Pair: currency.EMPTYPAIR, Error: currency.ErrCurrencyPairEmpty},
+ {Pair: currency.NewPair(currency.BTC, currency.NewCode("PERPETUAL")), Response: true},
+ {Pair: currency.NewPair(currency.NewCode("ETH"), currency.NewCode("FS-30DEC22_PERP")), Response: true},
+ },
+ asset.FutureCombo: {
+ {Pair: currency.NewPair(currency.NewCode("SOL"), currency.NewCode("FS-30DEC22_28OCT22"))},
+ },
+ asset.OptionCombo: {
+ {Pair: currency.NewPair(currency.NewCode(currencyBTC), currency.NewCode("STRG-21OCT22")), Error: futures.ErrNotPerpetualFuture},
+ {Pair: currency.EMPTYPAIR, Error: futures.ErrNotPerpetualFuture},
+ },
+ }
+ for assetType, instances := range assetPairToErrorMap {
+ for i := range instances {
+ is, err := d.IsPerpetualFutureCurrency(assetType, instances[i].Pair)
+ require.ErrorIsf(t, err, instances[i].Error, "expected %v, got %v for asset: %s pair: %s", instances[i].Error, err, assetType.String(), instances[i].Pair.String())
+ require.Equalf(t, is, instances[i].Response, "expected %v, got %v for asset: %s pair: %s", instances[i].Response, is, assetType.String(), instances[i].Pair.String())
+ }
+ }
+}
+
+func TestGetHistoricalFundingRates(t *testing.T) {
+ t.Parallel()
+ cp, err := currency.NewPairFromString("BTC-PERPETUAL")
+ require.NoError(t, err)
+ r := &fundingrate.HistoricalRatesRequest{
+ Asset: asset.Spot,
+ Pair: cp,
+ PaymentCurrency: currency.USDT,
+ StartDate: time.Now().Add(-time.Hour * 24 * 2),
+ EndDate: time.Now(),
+ }
+ _, err = d.GetHistoricalFundingRates(context.Background(), r)
+ require.ErrorIs(t, err, asset.ErrNotSupported)
+
+ r.Asset = asset.Futures
+ result, err := d.GetHistoricalFundingRates(context.Background(), r)
+ require.NoError(t, err)
+ assert.NotNil(t, result)
+}
+
+func TestMultipleCancelResponseUnmarshalJSON(t *testing.T) {
+ t.Parallel()
+ resp := &struct {
+ Result *MultipleCancelResponse `json:"result"`
+ }{}
+ data := `{ "jsonrpc": "2.0", "id": 8748, "result": 37 }`
+ err := json.Unmarshal([]byte(data), &resp)
+ require.NoError(t, err)
+ require.Equal(t, int64(37), resp.Result.CancelCount)
+ data = `{"jsonrpc":"2.0","id":1599612810505,"result":[{"instrument_name":"BTC_USDC","currency":"BTC_USDC","result":[{"is_rebalance":false,"risk_reducing":false,"order_type":"limit","creation_timestamp":1715302998260,"order_state":"cancelled","contracts":300000.0,"average_price":0.0,"post_only":false,"last_update_timestamp":1715303041949,"filled_amount":0.0,"replaced":false,"web":false,"api":true,"mmp":false,"cancel_reason":"user_request","instrument_name":"BTC_USDC","order_id":"BTC_USDC-13133482","max_show":30.0,"time_in_force":"good_til_cancelled","direction":"buy","amount":30.0,"price":30.0,"label":"test"}],"type":"limit"}]}`
+ err = json.Unmarshal([]byte(data), &resp)
+ require.NoError(t, err)
+ require.Equal(t, int64(1), resp.Result.CancelCount)
+ require.Len(t, resp.Result.CancelDetails, 1)
+ require.Len(t, resp.Result.CancelDetails, 1)
+}
+
+func TestGetResolutionFromInterval(t *testing.T) {
+ t.Parallel()
+ intervalStringMap := []struct {
+ Interval kline.Interval
+ IntervalString string
+ Error error
+ }{
+ {Interval: kline.HundredMilliseconds, IntervalString: "100ms"},
+ {Interval: kline.OneMin, IntervalString: "1"},
+ {Interval: kline.ThreeMin, IntervalString: "3"},
+ {Interval: kline.FiveMin, IntervalString: "5"},
+ {Interval: kline.TenMin, IntervalString: "10"},
+ {Interval: kline.FifteenMin, IntervalString: "15"},
+ {Interval: kline.ThirtyMin, IntervalString: "30"},
+ {Interval: kline.OneHour, IntervalString: "60"},
+ {Interval: kline.TwoHour, IntervalString: "120"},
+ {Interval: kline.ThreeHour, IntervalString: "180"},
+ {Interval: kline.SixHour, IntervalString: "360"},
+ {Interval: kline.TwelveHour, IntervalString: "720"},
+ {Interval: kline.OneDay, IntervalString: "1D"},
+ {Interval: kline.Raw, IntervalString: "raw"},
+ {Interval: kline.FourHour, Error: kline.ErrUnsupportedInterval},
+ }
+ for x := range intervalStringMap {
+ result, err := d.GetResolutionFromInterval(intervalStringMap[x].Interval)
+ require.ErrorIs(t, err, intervalStringMap[x].Error)
+ require.Equal(t, intervalStringMap[x].IntervalString, result)
+ }
+}
+
+func getAssetToPairMap(items asset.Item) map[asset.Item]currency.Pair {
+ newMap := make(map[asset.Item]currency.Pair)
+ for a := range assetTypeToPairsMap {
+ if a&items == a {
+ newMap[a] = assetTypeToPairsMap[a]
+ }
+ }
+ return newMap
+}
+
+func TestGetValidatedCurrencyCode(t *testing.T) {
+ t.Parallel()
+ pairs := map[currency.Pair]string{
+ currency.NewPairWithDelimiter(currencySOL, "21OCT22-20-C", "-"): currencySOL,
+ currency.NewPairWithDelimiter(currencyBTC, "PERPETUAL", "-"): currencyBTC,
+ currency.NewPairWithDelimiter(currencyETH, "PERPETUAL", "-"): currencyETH,
+ currency.NewPairWithDelimiter(currencySOL, "PERPETUAL", "-"): currencySOL,
+ currency.NewPairWithDelimiter("AVAX_USDC", "PERPETUAL", "-"): currencyUSDC,
+ currency.NewPairWithDelimiter(currencyBTC, "USDC", "_"): currencyBTC,
+ currency.NewPairWithDelimiter(currencyETH, "USDC", "_"): currencyETH,
+ currency.NewPairWithDelimiter("DOT", "USDC-PERPETUAL", "_"): currencyUSDC,
+ currency.NewPairWithDelimiter("DOT", "USDT-PERPETUAL", "_"): currencyUSDT,
+ currency.EMPTYPAIR: "any",
+ }
+ for x := range pairs {
+ result := getValidatedCurrencyCode(x)
+ require.Equal(t, pairs[x], result, "expected: %s actual : %s for currency pair: %v", x, result, pairs[x])
+ }
+}
diff --git a/exchanges/deribit/deribit_types.go b/exchanges/deribit/deribit_types.go
new file mode 100644
index 00000000..9af38187
--- /dev/null
+++ b/exchanges/deribit/deribit_types.go
@@ -0,0 +1,1555 @@
+package deribit
+
+import (
+ "encoding/json"
+ "errors"
+ "regexp"
+ "time"
+
+ "github.com/thrasher-corp/gocryptotrader/common/convert"
+ "github.com/thrasher-corp/gocryptotrader/types"
+)
+
+const (
+ sideBUY = "buy"
+ sideSELL = "sell"
+
+ // currencies
+
+ currencyBTC = "BTC"
+ currencyETH = "ETH"
+ currencySOL = "SOL"
+ currencyUSDC = "USDC"
+ currencyUSDT = "USDT"
+ currencyEURR = "EURR"
+)
+
+var (
+ alphaNumericRegExp = regexp.MustCompile("^[a-zA-Z0-9_]*$")
+
+ errUnsupportedIndexName = errors.New("unsupported index name")
+ errInvalidInstrumentID = errors.New("invalid instrument ID")
+ errInvalidInstrumentName = errors.New("invalid instrument name")
+ errInvalidComboID = errors.New("invalid combo ID")
+ errNoArgumentPassed = errors.New("no argument passed")
+ errInvalidAmount = errors.New("invalid amount, must be greater than 0")
+ errMissingNonce = errors.New("missing nonce")
+ errInvalidTradeRole = errors.New("invalid trade role, only 'maker' and 'taker' are allowed")
+ errInvalidPrice = errors.New("invalid trade price")
+ errInvalidCryptoAddress = errors.New("invalid crypto address")
+ errIntervalNotSupported = errors.New("interval not supported")
+ errInvalidID = errors.New("invalid id")
+ errInvalidMarginModel = errors.New("missing margin model")
+ errInvalidEmailAddress = errors.New("invalid email address")
+ errMalformedData = errors.New("malformed data")
+ errWebsocketConnectionNotAuthenticated = errors.New("websocket connection is not authenticated")
+ errResolutionNotSet = errors.New("resolution not set")
+ errInvalidDestinationID = errors.New("invalid destination id")
+ errUnsupportedChannel = errors.New("channels not supported")
+ errUnacceptableAPIKey = errors.New("unacceptable api key name")
+ errInvalidUsername = errors.New("new username has to be specified")
+ errSubAccountNameChangeFailed = errors.New("subaccount name change failed")
+ errLanguageIsRequired = errors.New("language is required")
+ errInvalidAPIKeyID = errors.New("invalid api key id")
+ errMaxScopeIsRequired = errors.New("max scope is required")
+ errTradeModeIsRequired = errors.New("self trading mode is required")
+ errUserIDRequired = errors.New("userID is required")
+ errInvalidOrderSideOrDirection = errors.New("invalid direction, only 'buy' or 'sell' are supported")
+ errDifferentInstruments = errors.New("given instruments should have the same currency")
+ errZeroTimestamp = errors.New("zero timestamps are not allowed")
+ errMissingBlockTradeID = errors.New("missing block trade id")
+ errMissingSubAccountID = errors.New("missing subaccount id")
+ errUnsupportedInstrumentFormat = errors.New("unsupported instrument type format")
+ errSessionNameRequired = errors.New("session_name is required")
+ errRefreshTokenRequired = errors.New("refresh token is required")
+ errSubjectIDRequired = errors.New("subject id is required")
+ errMissingSignature = errors.New("missing signature")
+
+ websocketRequestTimeout = time.Second * 30
+
+ baseCurrencies = []string{
+ currencyBTC,
+ currencyETH,
+ currencySOL,
+ currencyUSDC,
+ currencyUSDT,
+ currencyEURR,
+ }
+)
+
+// UnmarshalError is the struct which is used for unmarshalling errors
+type UnmarshalError struct {
+ Message string `json:"message"`
+ Data struct {
+ Reason string `json:"reason"`
+ }
+ Code int64 `json:"code"`
+}
+
+// BookSummaryData stores summary data
+type BookSummaryData struct {
+ InterestRate float64 `json:"interest_rate"`
+ AskPrice float64 `json:"ask_price"`
+ VolumeUSD float64 `json:"volume_usd"`
+ Volume float64 `json:"volume"`
+ QuoteCurrency string `json:"quote_currency"`
+ PriceChange float64 `json:"price_change"`
+ OpenInterest float64 `json:"open_interest"`
+ MidPrice float64 `json:"mid_price"`
+ MarkPrice float64 `json:"mark_price"`
+ Low float64 `json:"low"`
+ Last float64 `json:"last"`
+ InstrumentName string `json:"instrument_name"`
+ High float64 `json:"high"`
+ EstimatedDeliveryPrice float64 `json:"estimated_delivery_price"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ BidPrice float64 `json:"bid_price"`
+ BaseCurrency string `json:"base_currency"`
+ Funding8H float64 `json:"funding_8h,omitempty"`
+ CurrentFunding float64 `json:"current_funding,omitempty"`
+ UnderlyingIndex string `json:"underlying_index"`
+ UnderlyingPrice float64 `json:"underlying_price"`
+ VolumeNotional float64 `json:"volume_notional"`
+}
+
+// ContractSizeData stores contract size for given instrument
+type ContractSizeData struct {
+ ContractSize float64 `json:"contract_size"`
+}
+
+// CurrencyData stores data for currencies
+type CurrencyData struct {
+ CoinType string `json:"coin_type"`
+ Currency string `json:"currency"`
+ CurrencyLong string `json:"currency_long"`
+ FeePrecision int64 `json:"fee_precision"`
+ MinConfirmations int64 `json:"min_confirmations"`
+ MinWithdrawalFee float64 `json:"min_withdrawal_fee"`
+ WithdrawalFee float64 `json:"withdrawal_fee"`
+ WithdrawalPriorities []struct {
+ Value float64 `json:"value"`
+ Name string `json:"name"`
+ } `json:"withdrawal_priorities"`
+}
+
+// IndexDeliveryPrice store index delivery prices list.
+type IndexDeliveryPrice struct {
+ Data []DeliveryPriceData `json:"data"`
+ TotalRecords int64 `json:"records_total"`
+}
+
+// DeliveryPriceData stores index delivery_price
+type DeliveryPriceData struct {
+ Date string `json:"date"`
+ DeliveryPrice float64 `json:"delivery_price"`
+}
+
+// FundingChartData stores futures funding chart data
+type FundingChartData struct {
+ CurrentInterest float64 `json:"current_interest"`
+ Interest8H float64 `json:"interest_8h"`
+ Data []struct {
+ IndexPrice float64 `json:"index_price"`
+ Interest8H float64 `json:"interest_8h"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ } `json:"data"`
+}
+
+// FundingRateHistory represents a funding rate history item
+type FundingRateHistory struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ IndexPrice float64 `json:"index_price"` // Index price in base currency
+ PrevIndexPrice float64 `json:"prev_index_price"` // Previous index price in base currency
+ Interest8H float64 `json:"interest_8h"` // 8hour interest rate
+ Interest1H float64 `json:"interest_1h"` // 1hour interest rate
+}
+
+// HistoricalVolatilityData stores volatility data for requested symbols
+type HistoricalVolatilityData struct {
+ Timestamp float64
+ Value float64
+}
+
+// IndexPrice holds index price for the instruments
+type IndexPrice struct {
+ BTC float64 `json:"BTC"`
+ ETH float64 `json:"ETH"`
+ Edp float64 `json:"edp"`
+}
+
+// IndexPriceData gets index price data
+type IndexPriceData struct {
+ EstimatedDeliveryPrice float64 `json:"estimated_delivery_price"`
+ IndexPrice float64 `json:"index_price"`
+}
+
+// InstrumentData gets data for instruments
+type InstrumentData struct {
+ InstrumentName string `json:"instrument_name"`
+ BaseCurrency string `json:"base_currency"`
+ Kind string `json:"kind"`
+ OptionType string `json:"option_type"`
+ QuoteCurrency string `json:"quote_currency"`
+ BlockTradeCommission float64 `json:"block_trade_commission"`
+ ContractSize float64 `json:"contract_size"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ ExpirationTimestamp convert.ExchangeTime `json:"expiration_timestamp"`
+ IsActive bool `json:"is_active"`
+ Leverage float64 `json:"leverage"`
+ MaxLeverage float64 `json:"max_leverage"`
+ MakerCommission float64 `json:"maker_commission"`
+ MinimumTradeAmount float64 `json:"min_trade_amount"`
+ TickSize float64 `json:"tick_size"`
+ TakerCommission float64 `json:"taker_commission"`
+ Strike float64 `json:"strike"`
+ SettlementPeriod string `json:"settlement_period"`
+ SettlementCurrency string `json:"settlement_currency"`
+ RequestForQuote bool `json:"rfq"`
+ PriceIndex string `json:"price_index"`
+ InstrumentID int64 `json:"instrument_id"`
+ CounterCurrency string `json:"counter_currency"`
+ MaximumLiquidationCommission float64 `json:"max_liquidation_commission"`
+ FutureType string `json:"future_type"`
+ TickSizeSteps []struct {
+ AbovePrice float64 `json:"above_price"`
+ TickSize float64 `json:"tick_size"`
+ } `json:"tick_size_steps"`
+ BlockTradeTickSize float64 `json:"block_trade_tick_size"`
+ BlockTradeMinTradeAmount float64 `json:"block_trade_min_trade_amount"`
+ InstrumentType string `json:"instrument_type"`
+}
+
+// SettlementsData stores data for settlement futures
+type SettlementsData struct {
+ Settlements []struct {
+ Funded float64 `json:"funded"`
+ Funding float64 `json:"funding"`
+ IndexPrice float64 `json:"index_price"`
+ SessionTax float64 `json:"session_tax"`
+ SessionTaxRate float64 `json:"session_tax_rate"`
+ Socialized float64 `json:"socialized"`
+ SettlementType string `json:"type"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ SessionProfitLoss float64 `json:"session_profit_loss"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Position float64 `json:"position"`
+ MarkPrice float64 `json:"mark_price"`
+ InstrumentName string `json:"instrument_name"`
+ } `json:"settlements"`
+ Continuation string `json:"continuation"`
+}
+
+// PublicTradesData stores data for public trades
+type PublicTradesData struct {
+ Trades []struct {
+ TradeSeq float64 `json:"trade_seq"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection int64 `json:"tick_direction"`
+ Price float64 `json:"price"`
+ MarkPrice float64 `json:"mark_price"`
+ Liquidation string `json:"liquidation"`
+ ImpliedVolatility float64 `json:"iv"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ Direction string `json:"direction"`
+ BlockTradeID string `json:"block_trade_id"`
+ Amount float64 `json:"amount"`
+ } `json:"trades"`
+ HasMore bool `json:"has_more"`
+}
+
+// MarkPriceHistory stores data for mark price history
+type MarkPriceHistory struct {
+ Timestamp convert.ExchangeTime
+ MarkPriceValue float64
+}
+
+// UnmarshalJSON deserialises the JSON info, including the timestamp.
+func (a *MarkPriceHistory) UnmarshalJSON(data []byte) error {
+ var resp [2]float64
+ err := json.Unmarshal(data, &resp)
+ if err != nil {
+ return err
+ }
+ a.Timestamp = convert.ExchangeTime(time.UnixMilli(int64(resp[0])))
+ a.MarkPriceValue = resp[1]
+ return nil
+}
+
+// Orderbook stores orderbook data
+type Orderbook struct {
+ EstimatedDeliveryPrice float64 `json:"estimated_delivery_price"`
+ UnderlyingPrice float64 `json:"underlying_price"`
+ UnderlyingIndex string `json:"underlying_index"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Stats struct {
+ Volume float64 `json:"volume"`
+ PriceChange float64 `json:"price_change"`
+ Low float64 `json:"low"`
+ High float64 `json:"high"`
+ } `json:"stats"`
+ State string `json:"state"`
+ SettlementPrice float64 `json:"settlement_price"`
+ OpenInterest float64 `json:"open_interest"`
+ MinPrice float64 `json:"min_price"`
+ MaxPrice float64 `json:"max_price"`
+ MarkIV float64 `json:"mark_iv"`
+ MarkPrice float64 `json:"mark_price"`
+ LastPrice float64 `json:"last_price"`
+ InterestRate float64 `json:"interest_rate"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ GreeksData struct {
+ Delta float64 `json:"delta"`
+ Gamma float64 `json:"gamma"`
+ Rho float64 `json:"rho"`
+ Theta float64 `json:"theta"`
+ Vega float64 `json:"vega"`
+ } `json:"greeks"`
+ Funding8H float64 `json:"funding_8h"`
+ CurrentFunding float64 `json:"current_funding"`
+ ChangeID int64 `json:"change_id"`
+ Bids [][]float64 `json:"bids"`
+ Asks [][]float64 `json:"asks"`
+ BidIV float64 `json:"bid_iv"`
+ BestBidPrice float64 `json:"best_bid_price"`
+ BestBidAmount float64 `json:"best_bid_amount"`
+ BestAskAmount float64 `json:"best_ask_amount"`
+ AskIV float64 `json:"ask_iv"`
+}
+
+// TradeVolumesData stores data for trade volumes
+type TradeVolumesData struct {
+ PutsVolume float64 `json:"puts_volume"`
+ PutsVolume7D float64 `json:"puts_volume_7d"`
+ PutsVolume30D float64 `json:"puts_volume_30d"`
+ FuturesVolume7D float64 `json:"futures_volume_7d"`
+ FuturesVolume30D float64 `json:"futures_volume_30d"`
+ FuturesVolume float64 `json:"futures_volume"`
+ CurrencyPair string `json:"currency_pair"`
+ CallsVolume7D float64 `json:"calls_volume_7d"`
+ CallsVolume30D float64 `json:"calls_volume_30d"`
+ CallsVolume float64 `json:"calls_volume"`
+}
+
+// TVChartData stores trading view chart data
+type TVChartData struct {
+ Volume []float64 `json:"volume"`
+ Cost []float64 `json:"cost"`
+ Ticks []int64 `json:"ticks"` // Values of the time axis given in milliseconds since UNIX epoch
+ Status string `json:"status"`
+ Open []float64 `json:"open"`
+ Low []float64 `json:"low"`
+ High []float64 `json:"high"`
+ Close []float64 `json:"close"`
+}
+
+// VolatilityIndexRawData stores raw index data for volatility
+type VolatilityIndexRawData struct {
+ Data [][5]float64 `json:"data"`
+}
+
+// VolatilityIndexData stores index data for volatility
+type VolatilityIndexData struct {
+ TimestampMS time.Time `json:"timestamp"`
+ Open float64 `json:"open"`
+ High float64 `json:"high"`
+ Low float64 `json:"low"`
+ Close float64 `json:"close"`
+}
+
+// TickerData stores data for ticker
+type TickerData struct {
+ AskIV float64 `json:"ask_iv"`
+ BestAskAmount float64 `json:"best_ask_amount"`
+ BestAskPrice float64 `json:"best_ask_price"`
+ BestBidAmount float64 `json:"best_bid_amount"`
+ BestBidPrice float64 `json:"best_bid_price"`
+ BidIV float64 `json:"bid_iv"`
+ CurrentFunding float64 `json:"current_funding"`
+ DeliveryPrice float64 `json:"delivery_price"`
+ Funding8H float64 `json:"funding_8h"`
+ GreeksData struct {
+ Delta float64 `json:"delta"`
+ Gamma float64 `json:"gamma"`
+ Rho float64 `json:"rho"`
+ Theta float64 `json:"theta"`
+ Vega float64 `json:"vega"`
+ } `json:"greeks"`
+ IndexPrice float64 `json:"index_price"`
+ InstrumentName string `json:"instrument_name"`
+ LastPrice float64 `json:"last_price"`
+ MarkIV float64 `json:"mark_iv"`
+ MarkPrice float64 `json:"mark_price"`
+ MaxPrice float64 `json:"max_price"`
+ MinPrice float64 `json:"min_price"`
+ OpenInterest float64 `json:"open_interest"`
+ SettlementPrice float64 `json:"settlement_price"`
+ State string `json:"state"`
+ Stats struct {
+ VolumeNotional float64 `json:"volume_notional"`
+ VolumeUSD float64 `json:"volume_usd"`
+ Volume float64 `json:"volume"`
+ PriceChange float64 `json:"price_change"`
+ Low float64 `json:"low"`
+ High float64 `json:"high"`
+ } `json:"stats"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ UnderlyingIndex string `json:"underlying_index"`
+ UnderlyingPrice float64 `json:"underlying_price"`
+ EstimatedDeliveryPrice float64 `json:"estimated_delivery_price"`
+ InterestValue float64 `json:"interest_value"`
+}
+
+// CancelWithdrawalData stores cancel request data for a withdrawal
+type CancelWithdrawalData struct {
+ Address string `json:"address"`
+ Amount float64 `json:"amount"`
+ ConfirmedTimestamp convert.ExchangeTime `json:"confirmed_timestamp"`
+ CreatedTimestamp convert.ExchangeTime `json:"created_timestamp"`
+ Currency string `json:"currency"`
+ Fee float64 `json:"fee"`
+ ID int64 `json:"id"`
+ Priority float64 `json:"priority"`
+ Status string `json:"status"`
+ TransactionID int64 `json:"transaction_id"`
+ UpdatedTimestamp convert.ExchangeTime `json:"updated_timestamp"`
+}
+
+// DepositAddressData stores data of a deposit address
+type DepositAddressData struct {
+ Address string `json:"address"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ Currency string `json:"currency"`
+ Type string `json:"type"`
+}
+
+// DepositsData stores data of deposits
+type DepositsData struct {
+ Count int64 `json:"count"`
+ Data []struct {
+ Address string `json:"address"`
+ Amount float64 `json:"amount"`
+ Currency string `json:"currency"`
+ ReceivedTimestamp convert.ExchangeTime `json:"received_timestamp"`
+ State string `json:"state"`
+ TransactionID string `json:"transaction_id"`
+ UpdatedTimestamp convert.ExchangeTime `json:"updated_timestamp"`
+ } `json:"data"`
+}
+
+// TransfersData stores list of transfer data
+type TransfersData struct {
+ Count int64 `json:"count"`
+ Data []TransferData `json:"data"`
+}
+
+// TransferData stores data for a transfer
+type TransferData struct {
+ Amount float64 `json:"amount"`
+ CreatedTimestamp convert.ExchangeTime `json:"created_timestamp"`
+ Currency string `json:"currency"`
+ Direction string `json:"direction"`
+ ID int64 `json:"id"`
+ OtherSide string `json:"other_side"`
+ State string `json:"state"`
+ Type string `json:"type"`
+ UpdatedTimestamp convert.ExchangeTime `json:"updated_timestamp"`
+}
+
+// WithdrawData stores data of withdrawal
+type WithdrawData struct {
+ Address string `json:"address"`
+ Amount float64 `json:"amount"`
+ ConfirmedTimestamp convert.ExchangeTime `json:"confirmed_timestamp"`
+ CreatedTimestamp convert.ExchangeTime `json:"created_timestamp"`
+ Currency string `json:"currency"`
+ Fee float64 `json:"fee"`
+ ID int64 `json:"id"`
+ Priority float64 `json:"priority"`
+ State string `json:"state"`
+ TransactionID string `json:"transaction_id"`
+ UpdatedTimestamp convert.ExchangeTime `json:"updated_timestamp"`
+}
+
+// WithdrawalsData stores data of withdrawals
+type WithdrawalsData struct {
+ Count int64 `json:"count"`
+ Data []WithdrawData `json:"data"`
+}
+
+// TradeData stores a data for a private trade
+type TradeData struct {
+ TradeSequence int64 `json:"trade_seq"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection int64 `json:"tick_direction"`
+ State string `json:"state"`
+ SelfTrade bool `json:"self_trade"`
+ ReduceOnly bool `json:"reduce_only"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderID string `json:"order_id"`
+ MatchingID int64 `json:"matching_id"`
+ MarkPrice float64 `json:"mark_price"`
+ Liquidity string `json:"liquidity"`
+ Label string `json:"label"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ FeeCurrency string `json:"fee_currency"`
+ Fee float64 `json:"fee"`
+ Direction string `json:"direction"`
+ Amount float64 `json:"amount"`
+}
+
+// OrderData stores order data
+type OrderData struct {
+ Web bool `json:"web"`
+ TimeInForce string `json:"time_in_force"`
+ Replaced bool `json:"replaced"`
+ ReduceOnly bool `json:"reduce_only"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderState string `json:"order_state"`
+ OrderID string `json:"order_id"`
+ MaxShow float64 `json:"max_show"`
+ LastUpdateTimestamp convert.ExchangeTime `json:"last_update_timestamp"`
+ Label string `json:"label"`
+ IsLiquidation bool `json:"is_liquidation"`
+ InstrumentName string `json:"instrument_name"`
+ FilledAmount float64 `json:"filled_amount"`
+ Direction string `json:"direction"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ Commission float64 `json:"commission"`
+ AveragePrice float64 `json:"average_price"`
+ API bool `json:"api"`
+ Amount float64 `json:"amount"`
+ IsRebalance bool `json:"is_rebalance"`
+}
+
+// InitialMarginInfo represents an initial margin of an order.
+type InitialMarginInfo struct {
+ InitialMarginCurrency string `json:"initial_margin_currency"`
+ InitialMargin float64 `json:"initial_margin"`
+ OrderID string `json:"order_id"`
+}
+
+// PrivateTradeData stores data of a private buy, sell or edit
+type PrivateTradeData struct {
+ Trades []TradeData `json:"trades"`
+ Order OrderData `json:"order"`
+}
+
+// CancelResp represents the detail of canceled order.
+type CancelResp struct {
+ InstrumentName string `json:"instrument_name"`
+ Currency string `json:"currency"`
+ Result []PrivateCancelData `json:"result"`
+ Type string `json:"type"`
+}
+
+// PrivateCancelData stores data of a private cancel
+type PrivateCancelData struct {
+ Triggered bool `json:"triggered"`
+ Trigger string `json:"trigger"`
+ TimeInForce string `json:"time_in_force"`
+ TriggerPrice float64 `json:"trigger_price"`
+ ReduceOnly bool `json:"reduce_only"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Price types.Number `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderState string `json:"order_state"`
+ OrderID string `json:"order_id"`
+ MaxShow float64 `json:"max_show"`
+ LastUpdateTimestamp convert.ExchangeTime `json:"last_update_timestamp"`
+ Label string `json:"label"`
+ IsLiquidation bool `json:"is_liquidation"`
+ InstrumentName string `json:"instrument_name"`
+ Direction string `json:"direction"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ API bool `json:"api"`
+ Amount float64 `json:"amount"`
+ Web bool `json:"web"`
+ StopPrice float64 `json:"stop_price"`
+ Replaced bool `json:"replaced"`
+ IsRebalance bool `json:"is_rebalance"`
+ RiskReducing bool `json:"risk_reducing"`
+ Contracts float64 `json:"contracts"`
+ AveragePrice float64 `json:"average_price"`
+ FilledAmount float64 `json:"filled_amount"`
+ Mmp bool `json:"mmp"`
+ CancelReason string `json:"cancel_reason"`
+}
+
+// MultipleCancelResponse represents a response after cancelling multiple orders.
+type MultipleCancelResponse struct {
+ CancelCount int64
+ CancelDetails []CancelResp
+}
+
+// UnmarshalJSON deserializes order cancellation response into a MultipleCancelResponse instance.
+func (a *MultipleCancelResponse) UnmarshalJSON(data []byte) error {
+ var cancelCount int64
+ var cancelDetails []CancelResp
+ err := json.Unmarshal(data, &cancelDetails)
+ if err != nil {
+ err = json.Unmarshal(data, &cancelCount)
+ if err != nil {
+ return err
+ }
+ a.CancelCount = cancelCount
+ return nil
+ }
+ a.CancelDetails = cancelDetails
+ for a := range cancelDetails {
+ cancelCount += int64(len(cancelDetails[a].Result))
+ }
+ a.CancelCount = cancelCount
+ return nil
+}
+
+// MarginsData stores data for margin
+type MarginsData struct {
+ Buy float64 `json:"buy"`
+ MaxPrice float64 `json:"max_price"`
+ MinPrice float64 `json:"min_price"`
+ Sell float64 `json:"sell"`
+}
+
+// MMPConfigData gets the current configuration data for MMP
+type MMPConfigData struct {
+ Currency string `json:"currency"`
+ Interval int64 `json:"interval"`
+ FrozenTime int64 `json:"frozen_time"`
+ QuantityLimit float64 `json:"quantity_limit"`
+}
+
+// UserTradesData stores data of user trades
+type UserTradesData struct {
+ Trades []UserTradeData `json:"trades"`
+ HasMore bool `json:"has_more"`
+}
+
+// UserTradeData stores data of user trades
+type UserTradeData struct {
+ UnderlyingPrice float64 `json:"underlying_price"`
+ TradeSequence int64 `json:"trade_sequence"`
+ TradeInstrument string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection int64 `json:"tick_direction"`
+ State string `json:"state"`
+ SelfTrade bool `json:"self_trade"`
+ ReduceOnly bool `json:"reduce_only"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderID string `json:"order_id"`
+ MatchingID int64 `json:"matching_id"`
+ MarkPrice float64 `json:"mark_price"`
+ Liquidity string `json:"liquidity"`
+ IV float64 `json:"iv"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ FeeCurrency string `json:"fee_currency"`
+ Fee float64 `json:"fee"`
+ Direction string `json:"direction"`
+ Amount float64 `json:"amount"`
+}
+
+// PrivateSettlementsHistoryData stores data for private settlement history
+type PrivateSettlementsHistoryData struct {
+ Settlements []PrivateSettlementData `json:"settlements"`
+ Continuation string `json:"continuation"`
+}
+
+// PrivateSettlementData stores private settlement data
+type PrivateSettlementData struct {
+ Type string `json:"type"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ SessionProfitLoss float64 `json:"session_profit_loss"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Position float64 `json:"position"`
+ MarkPrice float64 `json:"mark_price"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+}
+
+// AccountSummaryData stores data of account summary for a given currency
+type AccountSummaryData struct {
+ Balance float64 `json:"balance"`
+ OptionsSessionUPL float64 `json:"options_session_upl"`
+ DepositAddress string `json:"deposit_address"`
+ OptionsGamma float64 `json:"options_gamma"`
+ OptionsTheta float64 `json:"options_theta"`
+ Username string `json:"username"`
+ Equity float64 `json:"equity"`
+ Type string `json:"type"`
+ Currency string `json:"currency"`
+ DeltaTotal float64 `json:"delta_total"`
+ FuturesSessionRPL float64 `json:"futures_session_rpl"`
+ PortfolioManagingEnabled bool `json:"portfolio_managing_enabled"`
+ TotalPL float64 `json:"total_pl"`
+ MarginBalance float64 `json:"margin_balance"`
+ TFAEnabled bool `json:"tfa_enabled"`
+ OptionsSessionRPL float64 `json:"options_session_rpl"`
+ OptionsDelta float64 `json:"options_delta"`
+ FuturesPL float64 `json:"futures_pl"`
+ ReferrerID string `json:"referrer_id"`
+ ID int64 `json:"id"`
+ SessionUPL float64 `json:"session_upl"`
+ AvailableWithdrawalFunds float64 `json:"available_withdrawal_funds"`
+ OptionsPL float64 `json:"options_pl"`
+ SystemName string `json:"system_name"`
+ Limits struct {
+ NonMatchingEngine struct {
+ Rate int64 `json:"rate"`
+ Burst int64 `json:"burst"`
+ } `json:"non_matching_engine"`
+ MatchingEngine struct {
+ Rate int64 `json:"rate"`
+ Burst int64 `json:"burst"`
+ } `json:"matching_engine"`
+ } `json:"limits"`
+ InitialMargin float64 `json:"initial_margin"`
+ ProjectedInitialMargin float64 `json:"projected_initial_margin"`
+ MaintenanceMargin float64 `json:"maintenance_margin"`
+ SessionRPL float64 `json:"session_rpl"`
+ InteruserTransfersEnabled bool `json:"interuser_transfers_enabled"`
+ OptionsVega float64 `json:"options_vega"`
+ ProjectedDeltaTotal float64 `json:"projected_delta_total"`
+ Email string `json:"email"`
+ FuturesSessionUPL float64 `json:"futures_session_upl"`
+ AvailableFunds float64 `json:"available_funds"`
+ OptionsValue float64 `json:"options_value"`
+ DeltaTotalMap map[string]float64 `json:"delta_total_map"`
+ ProjectedMaintenanceMargin float64 `json:"projected_maintenance_margin"`
+ EstimatedLiquidationRatio float64 `json:"estimated_liquidation_ratio"`
+ PortfolioMarginingEnabled bool `json:"portfolio_margining_enabled"`
+ EstimatedLiquidationRatioMap map[string]float64 `json:"estimated_liquidation_ratio_map"`
+ FeeBalance float64 `json:"fee_balance"`
+ SpotReserve float64 `json:"spot_reserve"`
+}
+
+// APIKeyData stores data regarding the api key
+type APIKeyData struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ MaxScope string `json:"max_scope"`
+ ID int64 `json:"id"`
+ Enabled bool `json:"enabled"`
+ Default bool `json:"default"`
+ ClientSecret string `json:"client_secret"`
+ ClientID string `json:"client_id"`
+ Name string `json:"name"`
+}
+
+// SubAccountData stores subaccount data
+type SubAccountData struct {
+ Email string `json:"email"`
+ ID int64 `json:"id"`
+ IsPassword bool `json:"is_password"`
+ LoginEnabled bool `json:"login_enabled"`
+ Portfolio map[string]SubAccountBalanceData `json:"portfolio"`
+ ReceiveNotifications bool `json:"receive_notifications"`
+ SystemName string `json:"system_name"`
+ TFAEnabled bool `json:"tfa_enabled"`
+ Type string `json:"type"`
+ Username string `json:"username"`
+}
+
+// SubAccountBalanceData represents the subaccount balance information for each currency.
+type SubAccountBalanceData struct {
+ AvailableFunds float64 `json:"available_funds"`
+ AvailableWithdrawalFunds float64 `json:"available_withdrawal_funds"`
+ Balance float64 `json:"balance"`
+ Currency string `json:"currency"`
+ Equity float64 `json:"equity"`
+ InitialMargin float64 `json:"initial_margin"`
+ MaintenanceMargin float64 `json:"maintenance_margin"`
+ MarginBalance float64 `json:"margin_balance"`
+}
+
+// AffiliateProgramInfo stores info of affiliate program
+type AffiliateProgramInfo struct {
+ Received map[string]float64 `json:"received"`
+ NumberOfAffiliates int64 `json:"number_of_affiliates"`
+ Link string `json:"link"`
+ IsEnabled bool `json:"is_enabled"`
+}
+
+// PositionData stores data for account's position
+type PositionData struct {
+ AveragePrice float64 `json:"average_price"`
+ Delta float64 `json:"delta"`
+ Direction string `json:"direction"`
+ EstimatedLiquidationPrice float64 `json:"estimated_liquidation_price"`
+ FloatingProfitLoss float64 `json:"floating_profit_loss"`
+ IndexPrice float64 `json:"index_price"`
+ InitialMargin float64 `json:"initial_margin"`
+ InstrumentName string `json:"instrument_name"`
+ Leverage float64 `json:"leverage"`
+ Kind string `json:"kind"`
+ MaintenanceMargin float64 `json:"maintenance_margin"`
+ MarkPrice float64 `json:"mark_price"`
+ OpenOrdersMargin float64 `json:"open_orders_margin"`
+ RealizedProfitLoss float64 `json:"realized_profit_loss"`
+ SettlementPrice float64 `json:"settlement_price"`
+ Size float64 `json:"size"`
+ SizeCurrency float64 `json:"size_currency"`
+ TotalProfitLoss float64 `json:"total_profit_loss"`
+ Theta float64 `json:"theta"`
+ Vega float64 `json:"vega"`
+ RealizedFunding float64 `json:"realized_funding"`
+ InterestValue float64 `json:"interest_value"`
+ Gamma float64 `json:"gamma"`
+ FloatingProfitAndLossUSD float64 `json:"floating_profit_loss_usd"`
+}
+
+// TransactionLogData stores information regarding an account transaction
+type TransactionLogData struct {
+ Username string `json:"username"`
+ UserSeq int64 `json:"user_seq"`
+ UserID int64 `json:"user_id"`
+ TransactionType string `json:"transaction_type"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Side string `json:"side"`
+ Price float64 `json:"price"`
+ Position float64 `json:"position"`
+ OrderID string `json:"order_id"`
+ InterestPL float64 `json:"interest_pl"`
+ InstrumentName string `json:"instrument_name"`
+ Info struct {
+ TransferType string `json:"transfer_type"`
+ OtherUserID int64 `json:"other_user_id"`
+ OtherUser string `json:"other_user"`
+ } `json:"info"`
+ ID int64 `json:"id"`
+ Equity float64 `json:"equity"`
+ Currency string `json:"currency"`
+ Commission float64 `json:"commission"`
+ Change float64 `json:"change"`
+ Cashflow float64 `json:"cashflow"`
+ Balance float64 `json:"balance"`
+}
+
+// TransactionsData stores multiple transaction logs
+type TransactionsData struct {
+ Logs []TransactionLogData `json:"logs"`
+ Continuation int64 `json:"continuation"`
+}
+
+// wsInput defines a request obj for the JSON-RPC login and gets a websocket
+// response
+type wsInput struct {
+ JSONRPCVersion string `json:"jsonrpc,omitempty"`
+ ID int64 `json:"id,omitempty"`
+ Method string `json:"method"`
+ Params map[string]interface{} `json:"params,omitempty"`
+}
+
+// WsRequest defines a request obj for the JSON-RPC endpoints and gets a websocket
+// response
+type WsRequest struct {
+ JSONRPCVersion string `json:"jsonrpc,omitempty"`
+ ID int64 `json:"id,omitempty"`
+ Method string `json:"method"`
+ Params interface{} `json:"params,omitempty"`
+}
+
+// WsSubscriptionInput defines a request obj for the JSON-RPC and gets a websocket
+// response
+type WsSubscriptionInput struct {
+ JSONRPCVersion string `json:"jsonrpc,omitempty"`
+ ID int64 `json:"id,omitempty"`
+ Method string `json:"method"`
+ Params map[string][]string `json:"params,omitempty"`
+}
+
+type wsResponse struct {
+ JSONRPCVersion string `json:"jsonrpc,omitempty"`
+ ID int64 `json:"id,omitempty"`
+ Result interface{} `json:"result,omitempty"`
+ Error struct {
+ Message string `json:"message,omitempty"`
+ Code int64 `json:"code,omitempty"`
+ Data interface{} `json:"data"`
+ } `json:"error,omitempty"`
+}
+
+type wsLoginResponse struct {
+ JSONRPCVersion string `json:"jsonrpc"`
+ ID int64 `json:"id"`
+ Method string `json:"method"`
+ Result map[string]interface{} `json:"result"`
+ Error *UnmarshalError `json:"error"`
+}
+
+type wsSubscriptionResponse struct {
+ JSONRPCVersion string `json:"jsonrpc"`
+ ID int64 `json:"id"`
+ Method string `json:"method"`
+ Result []string `json:"result"`
+}
+
+// RequestForQuote RFQs for instruments in given currency.
+type RequestForQuote struct {
+ TradedVolume float64 `json:"traded_volume"`
+ Amount float64 `json:"amount"`
+ Side string `json:"side"`
+ LastRFQTimestamp convert.ExchangeTime `json:"last_rfq_tstamp"`
+ InstrumentName string `json:"instrument_name"`
+}
+
+// ComboDetail retrieves information about a combo
+type ComboDetail struct {
+ ID string `json:"id"`
+ InstrumentID int64 `json:"instrument_id"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ StateTimestamp convert.ExchangeTime `json:"state_timestamp"`
+ State string `json:"state"`
+ Legs []struct {
+ InstrumentName string `json:"instrument_name"`
+ Amount float64 `json:"amount"`
+ } `json:"legs"`
+}
+
+// ComboParam represents a parameter to sell and buy combo.
+type ComboParam struct {
+ InstrumentName string `json:"instrument_name"`
+ Direction string `json:"direction"`
+ Amount float64 `json:"amount,string"`
+}
+
+// BlockTradeParam represents a block trade parameter.
+type BlockTradeParam struct {
+ Price float64 `json:"price"`
+ InstrumentName string `json:"instrument_name"`
+ Direction string `json:"direction,omitempty"`
+ Amount float64 `json:"amount"`
+}
+
+// BlockTradeData represents a user's block trade data.
+type BlockTradeData struct {
+ TradeSeq int64 `json:"trade_seq"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection int64 `json:"tick_direction"`
+ State string `json:"state"`
+ SelfTrade bool `json:"self_trade"`
+ Price float64 `json:"price"`
+ OrderType string `json:"order_type"`
+ OrderID string `json:"order_id"`
+ MatchingID interface{} `json:"matching_id"`
+ Liquidity string `json:"liquidity"`
+ OptionmpliedVolatility float64 `json:"iv,omitempty"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ FeeCurrency string `json:"fee_currency"`
+ Fee float64 `json:"fee"`
+ Direction string `json:"direction"`
+ BlockTradeID string `json:"block_trade_id"`
+ Amount float64 `json:"amount"`
+}
+
+// Announcement represents public announcements.
+type Announcement struct {
+ Title string `json:"title"`
+ PublicationTimestamp convert.ExchangeTime `json:"publication_timestamp"`
+ Important bool `json:"important"`
+ ID int64 `json:"id"`
+ Body string `json:"body"`
+
+ // Action taken by the platform administrators.
+ Action string `json:"action"`
+}
+
+// PortfolioMargin represents public portfolio margins.
+type PortfolioMargin struct {
+ VolumeRange []float64 `json:"vol_range"`
+ VegaPow2 float64 `json:"vega_pow2"`
+ VegaPow1 float64 `json:"vega_pow1"`
+ Skew float64 `json:"skew"`
+ PriceRange float64 `json:"price_range"`
+ OptSumContinguency float64 `json:"opt_sum_continguency"`
+ OptContinguency float64 `json:"opt_continguency"`
+ Kurtosis float64 `json:"kurtosis"`
+ IntRate float64 `json:"int_rate"`
+ InitialMarginFactor float64 `json:"initial_margin_factor"`
+ FtuContinguency float64 `json:"ftu_continguency"`
+ AtmRange float64 `json:"atm_range"`
+ ProjectedMarginPos float64 `json:"projected_margin_pos"`
+ ProjectedMargin float64 `json:"projected_margin"`
+ PositionSizes map[string]float64 `json:"position_sizes"`
+ Pls []float64 `json:"pls"`
+ PcoOpt float64 `json:"pco_opt"`
+ PcoFtu float64 `json:"pco_ftu"`
+ OptSummary []interface{} `json:"opt_summary"`
+ OptPls []float64 `json:"opt_pls"`
+ OptEntries []interface{} `json:"opt_entries"`
+ MarginPos float64 `json:"margin_pos"`
+ Margin float64 `json:"margin"`
+ FtuSummary []struct {
+ ShortTotalCost float64 `json:"short_total_cost"`
+ PlVec []float64 `json:"pl_vec"`
+ LongTotalCost float64 `json:"long_total_cost"`
+ ExpiryTimestamp convert.ExchangeTime `json:"exp_tstamp"`
+ } `json:"ftu_summary"`
+ FtuPls []float64 `json:"ftu_pls"`
+ FtuEntries []struct {
+ TotalCost float64 `json:"total_cost"`
+ Size float64 `json:"size"`
+ PlVec []float64 `json:"pl_vec"`
+ MarkPrice float64 `json:"mark_price"`
+ InstrumentName string `json:"instrument_name"`
+ ExpiryTimestamp convert.ExchangeTime `json:"exp_tstamp"`
+ } `json:"ftu_entries"`
+ CoOpt float64 `json:"co_opt"`
+ CoFtu float64 `json:"co_ftu"`
+ CalculationTimestamp convert.ExchangeTime `json:"calculation_timestamp"`
+}
+
+// AccessLog represents access log information.
+type AccessLog struct {
+ RecordsTotal int64 `json:"records_total"`
+ Data []AccessLogDetail `json:"data"`
+}
+
+// AccessLogDetail represents detailed access log information.
+type AccessLogDetail struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Result string `json:"result"`
+ IP string `json:"ip"`
+ ID int64 `json:"id"`
+ Country string `json:"country"`
+ City string `json:"city"`
+}
+
+// SubAccountDetail represents subaccount positions detail.
+type SubAccountDetail struct {
+ UID int64 `json:"uid"`
+ Positions []struct {
+ TotalProfitLoss float64 `json:"total_profit_loss"`
+ SizeCurrency float64 `json:"size_currency"`
+ Size float64 `json:"size"`
+ SettlementPrice float64 `json:"settlement_price"`
+ RealizedProfitLoss float64 `json:"realized_profit_loss"`
+ RealizedFunding float64 `json:"realized_funding"`
+ OpenOrdersMargin float64 `json:"open_orders_margin"`
+ MarkPrice float64 `json:"mark_price"`
+ MaintenanceMargin float64 `json:"maintenance_margin"`
+ Leverage float64 `json:"leverage"`
+ Kind string `json:"kind"`
+ InstrumentName string `json:"instrument_name"`
+ InitialMargin float64 `json:"initial_margin"`
+ IndexPrice float64 `json:"index_price"`
+ FloatingProfitLoss float64 `json:"floating_profit_loss"`
+ EstimatedLiquidationPrice float64 `json:"estimated_liquidation_price"`
+ Direction string `json:"direction"`
+ Delta float64 `json:"delta"`
+ AveragePrice float64 `json:"average_price"`
+ } `json:"positions"`
+}
+
+// UserLock represents a user lock information for currency.
+type UserLock struct {
+ Message string `json:"message"`
+ Locked bool `json:"locked"`
+ Currency string `json:"currency"`
+}
+
+// PortfolioMarginState represents a portfolio margin state information.
+type PortfolioMarginState struct {
+ MaintenanceMarginRate float64 `json:"maintenance_margin_rate"`
+ InitialMarginRate float64 `json:"initial_margin_rate"`
+ AvailableBalance float64 `json:"available_balance"`
+}
+
+// TogglePortfolioMarginResponse represents a response from toggling portfolio margin for currency.
+type TogglePortfolioMarginResponse struct {
+ OldState PortfolioMarginState `json:"old_state"`
+ NewState PortfolioMarginState `json:"new_state"`
+ Currency string `json:"currency"`
+}
+
+// BlockTradeResponse represents a block trade response.
+type BlockTradeResponse struct {
+ TradeSeq int64 `json:"trade_seq"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection int64 `json:"tick_direction"`
+ State string `json:"state"`
+ SelfTrade bool `json:"self_trade"`
+ ReduceOnly bool `json:"reduce_only"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderID string `json:"order_id"`
+ MatchingID string `json:"matching_id"`
+ MarkPrice float64 `json:"mark_price"`
+ Liquidity string `json:"liquidity"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ FeeCurrency string `json:"fee_currency"`
+ Fee float64 `json:"fee"`
+ Direction string `json:"direction"`
+ BlockTradeID string `json:"block_trade_id"`
+ Amount float64 `json:"amount"`
+}
+
+// BlockTradeMoveResponse represents block trade move response.
+type BlockTradeMoveResponse struct {
+ TargetSubAccountUID int64 `json:"target_uid"`
+ SourceSubAccountUID int64 `json:"source_uid"`
+ Price float64 `json:"price"`
+ InstrumentName string `json:"instrument_name"`
+ Direction string `json:"direction"`
+ Amount float64 `json:"amount"`
+}
+
+// WsResponse represents generalized websocket subscription push data and immediate websocket call responses.
+type WsResponse struct {
+ ID int64 `json:"id,omitempty"`
+ Params struct {
+ Data interface{} `json:"data"`
+ Channel string `json:"channel"`
+
+ // Used in heartbead and test_request messages.
+ Type string `json:"type"`
+ } `json:"params"`
+ Method string `json:"method"`
+ JSONRPCVersion string `json:"jsonrpc"`
+
+ // for status "ok" and "version" push data messages
+ Result interface{} `json:"result"`
+}
+
+// VersionInformation represents websocket version information
+type VersionInformation struct {
+ Version string `json:"version"`
+}
+
+// wsOrderbook represents orderbook push data for a book websocket subscription.
+type wsOrderbook struct {
+ Type string `json:"type"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ InstrumentName string `json:"instrument_name"`
+ ChangeID int64 `json:"change_id"`
+ Bids [][]interface{} `json:"bids"`
+ Asks [][]interface{} `json:"asks"`
+}
+
+// wsCandlestickData represents publicly available market data used to generate a TradingView candle chart.
+type wsCandlestickData struct {
+ Volume float64 `json:"volume"`
+ Tick int64 `json:"tick"`
+ Open float64 `json:"open"`
+ Low float64 `json:"low"`
+ High float64 `json:"high"`
+ Cost float64 `json:"cost"`
+ Close float64 `json:"close"`
+}
+
+// wsIndexPrice represents information about current value (price) for Deribit Index
+type wsIndexPrice struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Price float64 `json:"price"`
+ IndexName string `json:"index_name"`
+}
+
+// wsRankingPrice
+type wsRankingPrice struct {
+ Weight float64 `json:"weight"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Price float64 `json:"price"`
+ OriginalPrice float64 `json:"original_price"`
+ Identifier string `json:"identifier"`
+ Enabled bool `json:"enabled"`
+}
+
+// wsRankingPrices
+type wsRankingPrices []wsRankingPrice
+
+// wsPriceStatistics represents basic statistics about Deribit Index
+type wsPriceStatistics struct {
+ Low24H float64 `json:"low24h"`
+ IndexName string `json:"index_name"`
+ HighVolatility bool `json:"high_volatility"`
+ High24H float64 `json:"high24h"`
+ Change24H float64 `json:"change24h"`
+}
+
+// wsVolatilityIndex represents volatility index push data
+type wsVolatilityIndex struct {
+ Volatility float64 `json:"volatility"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ IndexName string `json:"index_name"`
+ EstimatedDelivery float64 `json:"estimated_delivery"`
+}
+
+// wsEstimatedExpirationPrice represents push data of ending price for given index.
+type wsEstimatedExpirationPrice struct {
+ Seconds int64 `json:"seconds"`
+ Price float64 `json:"price"`
+ IsEstimated bool `json:"is_estimated"`
+}
+
+// wsTicker represents changes in ticker (key information about the instrument).
+type wsTicker struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Stats struct {
+ VolumeUsd float64 `json:"volume_usd"`
+ Volume float64 `json:"volume"`
+ PriceChange float64 `json:"price_change"`
+ Low float64 `json:"low"`
+ High float64 `json:"high"`
+ } `json:"stats"`
+ State string `json:"state"`
+ SettlementPrice float64 `json:"settlement_price"`
+ OpenInterest float64 `json:"open_interest"`
+ MinPrice float64 `json:"min_price"`
+ MaxPrice float64 `json:"max_price"`
+ MarkPrice float64 `json:"mark_price"`
+ LastPrice float64 `json:"last_price"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ ImpliedBid float64 `json:"implied_bid"`
+ ImpliedAsk float64 `json:"implied_ask"`
+ EstimatedDeliveryPrice float64 `json:"estimated_delivery_price"`
+ ComboState string `json:"combo_state"`
+ BestBidPrice float64 `json:"best_bid_price"`
+ BestBidAmount float64 `json:"best_bid_amount"`
+ BestAskPrice float64 `json:"best_ask_price"`
+ BestAskAmount float64 `json:"best_ask_amount"`
+}
+
+// WsIncrementalTicker represents a ticker information for incremental ticker subscriptions.
+type WsIncrementalTicker struct {
+ Type string `json:"type"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Stats struct {
+ VolumeUsd float64 `json:"volume_usd"`
+ Volume float64 `json:"volume"`
+ PriceChange float64 `json:"price_change"`
+ } `json:"stats"`
+ MinPrice float64 `json:"min_price"`
+ MaxPrice float64 `json:"max_price"`
+ MarkPrice float64 `json:"mark_price"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ EstimatedDeliveryPrice float64 `json:"estimated_delivery_price"`
+ BestBidAmount float64 `json:"best_bid_amount"`
+ BestAskAmount float64 `json:"best_ask_amount"`
+
+ // For future_combo instruments
+ ImpliedAsk float64 `json:"implied_ask"`
+ ImpliedBid float64 `json:"implied_bid"`
+
+ UnderlyingPrice float64 `json:"underlying_price"`
+ UnderlyingIndex string `json:"underlying_index"`
+ State string `json:"state"`
+ SettlementPrice float64 `json:"settlement_price"`
+ OpenInterest float64 `json:"open_interest"`
+
+ MarkIv float64 `json:"mark_iv"`
+ LastPrice float64 `json:"last_price"`
+ InterestRate float64 `json:"interest_rate"`
+ Greeks struct {
+ Vega float64 `json:"vega"`
+ Theta float64 `json:"theta"`
+ Rho float64 `json:"rho"`
+ Gamma float64 `json:"gamma"`
+ Delta float64 `json:"delta"`
+ } `json:"greeks"`
+ ComboState string `json:"combo_state"`
+ BidIv float64 `json:"bid_iv"`
+ BestBidPrice float64 `json:"best_bid_price"`
+ BestAskPrice float64 `json:"best_ask_price"`
+ AskIv float64 `json:"ask_iv"`
+}
+
+// wsInstrumentState represents notifications about new or terminated instruments of given kind in given currency.
+type wsInstrumentState struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ State string `json:"state"`
+ InstrumentName string `json:"instrument_name"`
+}
+
+// wsMarkPriceOptions represents information about options markprices.
+type wsMarkPriceOptions struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ MarkPrice float64 `json:"mark_price"`
+ Iv float64 `json:"iv"`
+ InstrumentName string `json:"instrument_name"`
+}
+
+// wsPerpetualInterest represents current interest rate - but only for perpetual instruments.
+type wsPerpetualInterest struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Interest float64 `json:"interest"`
+ IndexPrice float64 `json:"index_price"`
+}
+
+// wsPlatformState holds Information whether unauthorized public requests are allowed
+type wsPlatformState struct {
+ AllowUnauthenticatedPublicRequests bool `json:"allow_unauthenticated_public_requests"`
+}
+
+// wsQuoteTickerInformation represents best bid/ask price and size.
+type wsQuoteTickerInformation struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ InstrumentName string `json:"instrument_name"`
+ BestBidPrice float64 `json:"best_bid_price"`
+ BestBidAmount float64 `json:"best_bid_amount"`
+ BestAskPrice float64 `json:"best_ask_price"`
+ BestAskAmount float64 `json:"best_ask_amount"`
+}
+
+// wsRequestForQuote represents a notifications about RFQs for instruments in given currency.
+type wsRequestForQuote struct {
+ State bool `json:"state"`
+ Side interface{} `json:"side"`
+ LastRFQTimestamp convert.ExchangeTime `json:"last_rfq_tstamp"`
+ InstrumentName string `json:"instrument_name"`
+ Amount interface{} `json:"amount"`
+}
+
+// wsTrade represents trades for an instrument.
+type wsTrade struct {
+ TradeSequence int64 `json:"trade_seq"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection float64 `json:"tick_direction"`
+ Price float64 `json:"price"`
+ MarkPrice float64 `json:"mark_price"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ Direction string `json:"direction"`
+ Amount float64 `json:"amount"`
+}
+
+// wsAccessLog represents security events related to the account
+type wsAccessLog struct {
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ Log string `json:"log"`
+ IP string `json:"ip"`
+ ID int64 `json:"id"`
+ Country string `json:"country"`
+ City string `json:"city"`
+}
+
+// wsChanges represents user's updates related to order, trades, etc. in an instrument.
+type wsChanges struct {
+ Trades []struct {
+ TradeSeq float64 `json:"trade_seq"`
+ TradeID string `json:"trade_id"`
+ Timestamp convert.ExchangeTime `json:"timestamp"`
+ TickDirection float64 `json:"tick_direction"`
+ State string `json:"state"`
+ SelfTrade bool `json:"self_trade"`
+ ReduceOnly bool `json:"reduce_only"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderID string `json:"order_id"`
+ MatchingID interface{} `json:"matching_id"`
+ MarkPrice float64 `json:"mark_price"`
+ Liquidity string `json:"liquidity"`
+ InstrumentName string `json:"instrument_name"`
+ IndexPrice float64 `json:"index_price"`
+ FeeCurrency string `json:"fee_currency"`
+ Fee float64 `json:"fee"`
+ Direction string `json:"direction"`
+ Amount float64 `json:"amount"`
+ } `json:"trades"`
+ Positions []WebsocketPosition `json:"positions"`
+ Orders []struct {
+ Web bool `json:"web"`
+ TimeInForce string `json:"time_in_force"`
+ Replaced bool `json:"replaced"`
+ ReduceOnly bool `json:"reduce_only"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OrderType string `json:"order_type"`
+ OrderState string `json:"order_state"`
+ OrderID string `json:"order_id"`
+ MaxShow float64 `json:"max_show"`
+ LastUpdateTimestamp convert.ExchangeTime `json:"last_update_timestamp"`
+ Label string `json:"label"`
+ IsLiquidation bool `json:"is_liquidation"`
+ InstrumentName string `json:"instrument_name"`
+ FilledAmount float64 `json:"filled_amount"`
+ Direction string `json:"direction"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ Commission float64 `json:"commission"`
+ AveragePrice float64 `json:"average_price"`
+ API bool `json:"api"`
+ Amount float64 `json:"amount"`
+ } `json:"orders"`
+ InstrumentName string `json:"instrument_name"`
+}
+
+// WebsocketPosition holds position information
+type WebsocketPosition struct {
+ TotalProfitLoss float64 `json:"total_profit_loss"`
+ SizeCurrency float64 `json:"size_currency"`
+ Size float64 `json:"size"`
+ SettlementPrice float64 `json:"settlement_price"`
+ RealizedProfitLoss float64 `json:"realized_profit_loss"`
+ RealizedFunding float64 `json:"realized_funding"`
+ OpenOrdersMargin float64 `json:"open_orders_margin"`
+ MarkPrice float64 `json:"mark_price"`
+ MaintenanceMargin float64 `json:"maintenance_margin"`
+ Leverage float64 `json:"leverage"`
+ Kind string `json:"kind"`
+ InterestValue float64 `json:"interest_value"`
+ InstrumentName string `json:"instrument_name"`
+ InitialMargin float64 `json:"initial_margin"`
+ IndexPrice float64 `json:"index_price"`
+ FloatingProfitLoss float64 `json:"floating_profit_loss"`
+ Direction string `json:"direction"`
+ Delta float64 `json:"delta"`
+ AveragePrice float64 `json:"average_price"`
+}
+
+// WsUserLock represents a notification data when account is locked/unlocked
+type WsUserLock struct {
+ Locked bool `json:"locked"`
+ Currency string `json:"currency"`
+}
+
+// WsMMPTrigger represents mmp trigger data.
+type WsMMPTrigger struct {
+ FrozenUntil int64 `json:"frozen_until"`
+ Currency string `json:"currency"`
+}
+
+// WsOrder represents changes in user's orders for given instrument.
+type WsOrder struct {
+ TimeInForce string `json:"time_in_force"`
+ Replaced bool `json:"replaced"`
+ ReduceOnly bool `json:"reduce_only"`
+ ProfitLoss float64 `json:"profit_loss"`
+ Price float64 `json:"price"`
+ PostOnly bool `json:"post_only"`
+ OriginalOrderType string `json:"original_order_type"`
+ OrderType string `json:"order_type"`
+ OrderState string `json:"order_state"`
+ OrderID string `json:"order_id"`
+ MaxShow float64 `json:"max_show"`
+ LastUpdateTimestamp convert.ExchangeTime `json:"last_update_timestamp"`
+ Label string `json:"label"`
+ IsLiquidation bool `json:"is_liquidation"`
+ InstrumentName string `json:"instrument_name"`
+ FilledAmount float64 `json:"filled_amount"`
+ Direction string `json:"direction"`
+ CreationTimestamp convert.ExchangeTime `json:"creation_timestamp"`
+ Commission float64 `json:"commission"`
+ AveragePrice float64 `json:"average_price"`
+ API bool `json:"api"`
+ Amount float64 `json:"amount"`
+}
+
+// wsUserPortfolio represents current user portfolio
+type wsUserPortfolio struct {
+ TotalPl float64 `json:"total_pl"`
+ SessionUpl float64 `json:"session_upl"`
+ SessionRpl float64 `json:"session_rpl"`
+ ProjectedMaintenanceMargin float64 `json:"projected_maintenance_margin"`
+ ProjectedInitialMargin float64 `json:"projected_initial_margin"`
+ ProjectedDeltaTotal float64 `json:"projected_delta_total"`
+ PortfolioMarginingEnabled bool `json:"portfolio_margining_enabled"`
+ OptionsVega float64 `json:"options_vega"`
+ OptionsValue float64 `json:"options_value"`
+ OptionsTheta float64 `json:"options_theta"`
+ OptionsSessionUpl float64 `json:"options_session_upl"`
+ OptionsSessionRpl float64 `json:"options_session_rpl"`
+ OptionsPl float64 `json:"options_pl"`
+ OptionsGamma float64 `json:"options_gamma"`
+ OptionsDelta float64 `json:"options_delta"`
+ MarginBalance float64 `json:"margin_balance"`
+ MaintenanceMargin float64 `json:"maintenance_margin"`
+ InitialMargin float64 `json:"initial_margin"`
+ FuturesSessionUpl float64 `json:"futures_session_upl"`
+ FuturesSessionRpl float64 `json:"futures_session_rpl"`
+ FuturesPl float64 `json:"futures_pl"`
+ EstimatedLiquidationRatio float64 `json:"estimated_liquidation_ratio"`
+ Equity float64 `json:"equity"`
+ DeltaTotal float64 `json:"delta_total"`
+ Currency string `json:"currency"`
+ Balance float64 `json:"balance"`
+ AvailableWithdrawalFunds float64 `json:"available_withdrawal_funds"`
+ AvailableFunds float64 `json:"available_funds"`
+}
+
+// OrderBuyAndSellParams represents request parameters for submit order.
+type OrderBuyAndSellParams struct {
+ OrderID string `json:"order_id,omitempty"`
+ Instrument string `json:"instrument_name,omitempty"`
+ Amount float64 `json:"amount,omitempty"`
+ OrderType string `json:"order_type,omitempty"`
+ Price float64 `json:"price,omitempty"`
+ Label string `json:"label,omitempty"`
+ TimeInForce string `json:"time_in_force,omitempty"`
+ MaxShow float64 `json:"max_show,omitempty"`
+ PostOnly bool `json:"post_only,omitempty"`
+ RejectPostOnly bool `json:"reject_post_only,omitempty"`
+ ReduceOnly bool `json:"reduce_only,omitempty"`
+ MMP bool `json:"mmp,omitempty"`
+ TriggerPrice float64 `json:"trigger_price,omitempty"`
+ Trigger string `json:"trigger,omitempty"`
+ Advanced string `json:"advanced,omitempty"`
+}
+
+// ErrInfo represents an error response messages
+type ErrInfo struct {
+ Message string `json:"message"`
+ Data struct {
+ Param string `json:"param"`
+ Reason string `json:"reason"`
+ } `json:"data"`
+ Code int64 `json:"code"`
+}
+
+// CustodyAccount retrieves user custody accounts list.
+type CustodyAccount struct {
+ Name string `json:"name"`
+ Currency string `json:"currency"`
+ ClientID string `json:"client_id"`
+ Balance float64 `json:"balance"`
+ WithdrawalsRequireSecurityKey bool `json:"withdrawals_require_security_key"`
+ PendingWithdrawalBalance float64 `json:"pending_withdrawal_balance"`
+ AutoDeposit bool `json:"auto_deposit"`
+}
+
+// LockedCurrenciesStatus represents locked currencies status information.
+type LockedCurrenciesStatus struct {
+ LockedCurrencies []string `json:"locked_currencies"`
+ Locked string `json:"locked"`
+}
+
+// Info holds version information
+type Info struct {
+ Version string `json:"version"`
+}
+
+// CancelOnDisconnect holds scope and status information for cancel-on-disconnect
+type CancelOnDisconnect struct {
+ Scope string `json:"scope"`
+ Enable bool `json:"enabled"`
+}
+
+// RefreshTokenInfo holds access token information.
+type RefreshTokenInfo struct {
+ AccessToken string `json:"access_token"`
+ ExpiresInSeconds int64 `json:"expires_in"`
+ RefreshToken string `json:"refresh_token"`
+ Scope string `json:"scope"`
+ TokenType string `json:"token_type"`
+}
diff --git a/exchanges/deribit/deribit_websocket.go b/exchanges/deribit/deribit_websocket.go
new file mode 100644
index 00000000..fae7bf50
--- /dev/null
+++ b/exchanges/deribit/deribit_websocket.go
@@ -0,0 +1,1279 @@
+package deribit
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/common/crypto"
+ "github.com/thrasher-corp/gocryptotrader/currency"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/asset"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/nonce"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/order"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/stream"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/trade"
+ "github.com/thrasher-corp/gocryptotrader/log"
+)
+
+var deribitWebsocketAddress = "wss://www.deribit.com/ws" + deribitAPIVersion
+
+const (
+ rpcVersion = "2.0"
+ rateLimit = 20
+ errAuthFailed = 1002
+
+ // public websocket channels
+ announcementsChannel = "announcements"
+ orderbookChannel = "book"
+ chartTradesChannel = "chart.trades"
+ priceIndexChannel = "deribit_price_index"
+ priceRankingChannel = "deribit_price_ranking"
+ priceStatisticsChannel = "deribit_price_statistics"
+ volatilityIndexChannel = "deribit_volatility_index"
+ estimatedExpirationPriceChannel = "estimated_expiration_price"
+ incrementalTickerChannel = "incremental_ticker"
+ instrumentStateChannel = "instrument.state"
+ markPriceOptionsChannel = "markprice.options"
+ perpetualChannel = "perpetual."
+ platformStateChannel = "platform_state"
+ platformStatePublicMethodsStateChannel = "platform_state.public_methods_state"
+ quoteChannel = "quote"
+ requestForQuoteChannel = "rfq"
+ tickerChannel = "ticker."
+ tradesChannel = "trades."
+ tradesWithKindChannel = "trades"
+
+ // private websocket channels
+ userAccessLogChannel = "user.access_log"
+ userChangesInstrumentsChannel = "user.changes."
+ userChangesCurrencyChannel = "user.changes"
+ userLockChannel = "user.lock"
+ userMMPTriggerChannel = "user.mmp_trigger"
+ rawUserOrdersChannel = "user.orders.%s.raw"
+ userOrdersWithIntervalChannel = "user.orders."
+ rawUsersOrdersKindCurrencyChannel = "user.orders.%s.%s.raw"
+ rawUsersOrdersWithKindCurrencyAndIntervalChannel = "user.orders"
+ userPortfolioChannel = "user.portfolio"
+ userTradesChannelByInstrument = "user.trades."
+ userTradesByKindCurrencyAndIntervalChannel = "user.trades"
+)
+
+var (
+ defaultSubscriptions = []string{
+ chartTradesChannel, // chart trades channel to fetch candlestick data.
+ orderbookChannel,
+ tickerChannel,
+ tradesWithKindChannel,
+ }
+
+ indexENUMS = []string{"ada_usd", "algo_usd", "avax_usd", "bch_usd", "bnb_usd", "btc_usd", "doge_usd", "dot_usd", "eth_usd", "link_usd", "ltc_usd", "luna_usd", "matic_usd", "near_usd", "shib_usd", "sol_usd", "trx_usd", "uni_usd", "usdc_usd", "xrp_usd", "ada_usdc", "bch_usdc", "algo_usdc", "avax_usdc", "btc_usdc", "doge_usdc", "dot_usdc", "bch_usdc", "bnb_usdc", "eth_usdc", "link_usdc", "ltc_usdc", "luna_usdc", "matic_usdc", "near_usdc", "shib_usdc", "sol_usdc", "trx_usdc", "uni_usdc", "xrp_usdc", "btcdvol_usdc", "ethdvol_usdc"}
+
+ pingMessage = WsSubscriptionInput{
+ ID: 2,
+ JSONRPCVersion: rpcVersion,
+ Method: "public/test",
+ Params: map[string][]string{},
+ }
+ setHeartBeatMessage = wsInput{
+ ID: 1,
+ JSONRPCVersion: rpcVersion,
+ Method: "public/set_heartbeat",
+ Params: map[string]interface{}{
+ "interval": 15,
+ },
+ }
+)
+
+// WsConnect starts a new connection with the websocket API
+func (d *Deribit) WsConnect() error {
+ if !d.Websocket.IsEnabled() || !d.IsEnabled() {
+ return stream.ErrWebsocketNotEnabled
+ }
+ var dialer websocket.Dialer
+ err := d.Websocket.Conn.Dial(&dialer, http.Header{})
+ if err != nil {
+ return err
+ }
+ d.Websocket.Wg.Add(1)
+ go d.wsReadData()
+ if d.Websocket.CanUseAuthenticatedEndpoints() {
+ err = d.wsLogin(context.TODO())
+ if err != nil {
+ log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", d.Name, err)
+ d.Websocket.SetCanUseAuthenticatedEndpoints(false)
+ }
+ }
+ return d.Websocket.Conn.SendJSONMessage(setHeartBeatMessage)
+}
+
+func (d *Deribit) wsLogin(ctx context.Context) error {
+ if !d.IsWebsocketAuthenticationSupported() {
+ return fmt.Errorf("%v AuthenticatedWebsocketAPISupport not enabled", d.Name)
+ }
+ creds, err := d.GetCredentials(ctx)
+ if err != nil {
+ return err
+ }
+ d.Websocket.SetCanUseAuthenticatedEndpoints(true)
+ n := d.Requester.GetNonce(nonce.UnixNano).String()
+ strTS := strconv.FormatInt(time.Now().UnixMilli(), 10)
+ str2Sign := strTS + "\n" + n + "\n"
+ hmac, err := crypto.GetHMAC(crypto.HashSHA256,
+ []byte(str2Sign),
+ []byte(creds.Secret))
+ if err != nil {
+ return err
+ }
+
+ request := wsInput{
+ JSONRPCVersion: rpcVersion,
+ Method: "public/auth",
+ ID: d.Websocket.Conn.GenerateMessageID(false),
+ Params: map[string]interface{}{
+ "grant_type": "client_signature",
+ "client_id": creds.Key,
+ "timestamp": strTS,
+ "nonce": n,
+ "signature": crypto.HexEncodeToString(hmac),
+ },
+ }
+ resp, err := d.Websocket.Conn.SendMessageReturnResponse(request.ID, request)
+ if err != nil {
+ d.Websocket.SetCanUseAuthenticatedEndpoints(false)
+ return err
+ }
+ var response wsLoginResponse
+ err = json.Unmarshal(resp, &response)
+ if err != nil {
+ return fmt.Errorf("%v %v", d.Name, err)
+ }
+ if response.Error != nil && (response.Error.Code > 0 || response.Error.Message != "") {
+ return fmt.Errorf("%v Error:%v Message:%v", d.Name, response.Error.Code, response.Error.Message)
+ }
+ return nil
+}
+
+// wsReadData receives and passes on websocket messages for processing
+func (d *Deribit) wsReadData() {
+ defer d.Websocket.Wg.Done()
+
+ for {
+ resp := d.Websocket.Conn.ReadMessage()
+ if resp.Raw == nil {
+ return
+ }
+
+ err := d.wsHandleData(resp.Raw)
+ if err != nil {
+ d.Websocket.DataHandler <- err
+ }
+ }
+}
+
+func (d *Deribit) wsHandleData(respRaw []byte) error {
+ var response WsResponse
+ err := json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return fmt.Errorf("%s - err %s could not parse websocket data: %s", d.Name, err, respRaw)
+ }
+ if response.Method == "heartbeat" {
+ return d.Websocket.Conn.SendJSONMessage(pingMessage)
+ }
+ if response.ID > 2 {
+ if !d.Websocket.Match.IncomingWithData(response.ID, respRaw) {
+ return fmt.Errorf("can't send ws incoming data to Matched channel with RequestID: %d", response.ID)
+ }
+ return nil
+ } else if response.ID > 0 {
+ return nil
+ }
+ channels := strings.Split(response.Params.Channel, ".")
+ switch channels[0] {
+ case "announcements":
+ announcement := &Announcement{}
+ response.Params.Data = announcement
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ d.Websocket.DataHandler <- announcement
+ case "book":
+ return d.processOrderbook(respRaw, channels)
+ case "chart":
+ return d.processCandleChart(respRaw, channels)
+ case "deribit_price_index":
+ indexPrice := &wsIndexPrice{}
+ return d.processData(respRaw, indexPrice)
+ case "deribit_price_ranking":
+ priceRankings := &wsRankingPrices{}
+ return d.processData(respRaw, priceRankings)
+ case "deribit_price_statistics":
+ priceStatistics := &wsPriceStatistics{}
+ return d.processData(respRaw, priceStatistics)
+ case "deribit_volatility_index":
+ volatilityIndex := &wsVolatilityIndex{}
+ return d.processData(respRaw, volatilityIndex)
+ case "estimated_expiration_price":
+ estimatedExpirationPrice := &wsEstimatedExpirationPrice{}
+ return d.processData(respRaw, estimatedExpirationPrice)
+ case "incremental_ticker":
+ return d.processIncrementalTicker(respRaw, channels)
+ case "instrument":
+ instrumentState := &wsInstrumentState{}
+ return d.processData(respRaw, instrumentState)
+ case "markprice":
+ markPriceOptions := []wsMarkPriceOptions{}
+ return d.processData(respRaw, markPriceOptions)
+ case "perpetual":
+ perpetualInterest := &wsPerpetualInterest{}
+ return d.processData(respRaw, perpetualInterest)
+ case platformStateChannel:
+ platformState := &wsPlatformState{}
+ return d.processData(respRaw, platformState)
+ case "quote": // Quote ticker information.
+ return d.processQuoteTicker(respRaw, channels)
+ case "rfq":
+ rfq := &wsRequestForQuote{}
+ return d.processData(respRaw, rfq)
+ case "ticker":
+ return d.processInstrumentTicker(respRaw, channels)
+ case "trades":
+ return d.processTrades(respRaw, channels)
+ case "user":
+ switch channels[1] {
+ case "access_log":
+ accessLog := &wsAccessLog{}
+ return d.processData(respRaw, accessLog)
+ case "changes":
+ return d.processChanges(respRaw, channels)
+ case "lock":
+ userLock := &WsUserLock{}
+ return d.processData(respRaw, userLock)
+ case "mmp_trigger":
+ data := &WsMMPTrigger{
+ Currency: channels[2],
+ }
+ return d.processData(respRaw, data)
+ case "orders":
+ return d.processOrders(respRaw, channels)
+ case "portfolio":
+ portfolio := &wsUserPortfolio{}
+ return d.processData(respRaw, portfolio)
+ case "trades":
+ return d.processTrades(respRaw, channels)
+ default:
+ d.Websocket.DataHandler <- stream.UnhandledMessageWarning{
+ Message: d.Name + stream.UnhandledMessage + string(respRaw),
+ }
+ return nil
+ }
+ case "public/test", "public/set_heartbeat":
+ default:
+ switch result := response.Result.(type) {
+ case string:
+ if result == "ok" {
+ return nil
+ }
+ default:
+ d.Websocket.DataHandler <- stream.UnhandledMessageWarning{
+ Message: d.Name + stream.UnhandledMessage + string(respRaw),
+ }
+ return nil
+ }
+ }
+ return nil
+}
+
+func (d *Deribit) processOrders(respRaw []byte, channels []string) error {
+ var currencyPair currency.Pair
+ var err error
+ var a asset.Item
+ switch len(channels) {
+ case 4:
+ currencyPair, err = currency.NewPairFromString(channels[2])
+ if err != nil {
+ return err
+ }
+ case 5:
+ a, err = d.StringToAssetKind(channels[2])
+ if err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("%w, expected format 'user.orders.{instrument_name}.raw, user.orders.{instrument_name}.{interval}, user.orders.{kind}.{currency}.raw, or user.orders.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
+ }
+ var response WsResponse
+ orderData := []WsOrder{}
+ response.Params.Data = orderData
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ orderDetails := make([]order.Detail, len(orderData))
+ for x := range orderData {
+ oType, err := order.StringToOrderType(orderData[x].OrderType)
+ if err != nil {
+ return err
+ }
+ side, err := order.StringToOrderSide(orderData[x].Direction)
+ if err != nil {
+ return err
+ }
+ status, err := order.StringToOrderStatus(orderData[x].OrderState)
+ if err != nil {
+ return err
+ }
+ if a != asset.Empty {
+ currencyPair, err = currency.NewPairFromString(orderData[x].InstrumentName)
+ if err != nil {
+ return err
+ }
+ }
+ a, err = guessAssetTypeFromInstrument(currencyPair)
+ if err != nil {
+ return err
+ }
+ orderDetails[x] = order.Detail{
+ Price: orderData[x].Price,
+ Amount: orderData[x].Amount,
+ ExecutedAmount: orderData[x].FilledAmount,
+ RemainingAmount: orderData[x].Amount - orderData[x].FilledAmount,
+ Exchange: d.Name,
+ OrderID: orderData[x].OrderID,
+ Type: oType,
+ Side: side,
+ Status: status,
+ AssetType: a,
+ Date: orderData[x].CreationTimestamp.Time(),
+ LastUpdated: orderData[x].LastUpdateTimestamp.Time(),
+ Pair: currencyPair,
+ }
+ }
+ d.Websocket.DataHandler <- orderDetails
+ return nil
+}
+
+func (d *Deribit) processChanges(respRaw []byte, channels []string) error {
+ var response WsResponse
+ changeData := &wsChanges{}
+ response.Params.Data = changeData
+ err := json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ var currencyPair currency.Pair
+ var a asset.Item
+ switch len(channels) {
+ case 4:
+ currencyPair, err = currency.NewPairFromString(channels[2])
+ if err != nil {
+ return err
+ }
+ case 5:
+ a, err = d.StringToAssetKind(channels[2])
+ if err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
+ }
+ tradeDatas := make([]trade.Data, len(changeData.Trades))
+ for x := range changeData.Trades {
+ var side order.Side
+ side, err = order.StringToOrderSide(changeData.Trades[x].Direction)
+ if err != nil {
+ return err
+ }
+ if currencyPair.IsEmpty() {
+ currencyPair, err = currency.NewPairFromString(changeData.Trades[x].InstrumentName)
+ if err != nil {
+ return err
+ }
+ }
+ if a == asset.Empty {
+ a, err = guessAssetTypeFromInstrument(currencyPair)
+ if err != nil {
+ return err
+ }
+ }
+ tradeDatas[x] = trade.Data{
+ CurrencyPair: currencyPair,
+ Exchange: d.Name,
+ Timestamp: changeData.Trades[x].Timestamp.Time(),
+ Price: changeData.Trades[x].Price,
+ Amount: changeData.Trades[x].Amount,
+ Side: side,
+ TID: changeData.Trades[x].TradeID,
+ AssetType: a,
+ }
+ }
+ err = trade.AddTradesToBuffer(d.Name, tradeDatas...)
+ if err != nil {
+ return err
+ }
+ orders := make([]order.Detail, len(changeData.Orders))
+ for x := range orders {
+ oType, err := order.StringToOrderType(changeData.Orders[x].OrderType)
+ if err != nil {
+ return err
+ }
+ side, err := order.StringToOrderSide(changeData.Orders[x].Direction)
+ if err != nil {
+ return err
+ }
+ status, err := order.StringToOrderStatus(changeData.Orders[x].OrderState)
+ if err != nil {
+ return err
+ }
+ if a != asset.Empty {
+ currencyPair, err = currency.NewPairFromString(changeData.Orders[x].InstrumentName)
+ if err != nil {
+ return err
+ }
+ } else {
+ a, err = guessAssetTypeFromInstrument(currencyPair)
+ if err != nil {
+ return err
+ }
+ }
+ orders[x] = order.Detail{
+ Price: changeData.Orders[x].Price,
+ Amount: changeData.Orders[x].Amount,
+ ExecutedAmount: changeData.Orders[x].FilledAmount,
+ RemainingAmount: changeData.Orders[x].Amount - changeData.Orders[x].FilledAmount,
+ Exchange: d.Name,
+ OrderID: changeData.Orders[x].OrderID,
+ Type: oType,
+ Side: side,
+ Status: status,
+ AssetType: a,
+ Date: changeData.Orders[x].CreationTimestamp.Time(),
+ LastUpdated: changeData.Orders[x].LastUpdateTimestamp.Time(),
+ Pair: currencyPair,
+ }
+ }
+ d.Websocket.DataHandler <- orders
+ d.Websocket.DataHandler <- changeData.Positions
+ return nil
+}
+
+func (d *Deribit) processQuoteTicker(respRaw []byte, channels []string) error {
+ cp, err := currency.NewPairFromString(channels[1])
+ if err != nil {
+ return err
+ }
+ var response WsResponse
+ quoteTicker := &wsQuoteTickerInformation{}
+ response.Params.Data = quoteTicker
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ a, err := guessAssetTypeFromInstrument(cp)
+ if err != nil {
+ return err
+ }
+ d.Websocket.DataHandler <- &ticker.Price{
+ ExchangeName: d.Name,
+ Pair: cp,
+ AssetType: a,
+ LastUpdated: quoteTicker.Timestamp.Time(),
+ Bid: quoteTicker.BestBidPrice,
+ Ask: quoteTicker.BestAskPrice,
+ BidSize: quoteTicker.BestBidAmount,
+ AskSize: quoteTicker.BestAskAmount,
+ }
+ return nil
+}
+
+func (d *Deribit) processTrades(respRaw []byte, channels []string) error {
+ var err error
+ var currencyPair currency.Pair
+ var a asset.Item
+ switch {
+ case (len(channels) == 3 && channels[0] == "trades") || (len(channels) == 4 && channels[0] == "user"):
+ currencyPair, err = currency.NewPairFromString(channels[len(channels)-2])
+ if err != nil {
+ return err
+ }
+ case (len(channels) == 4 && channels[0] == "trades") || (len(channels) == 5 && channels[0] == "user"):
+ a, err = d.StringToAssetKind(channels[len(channels)-3])
+ if err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("%w, expected format 'trades.{instrument_name}.{interval} or trades.{kind}.{currency}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
+ }
+ var response WsResponse
+ tradeList := []wsTrade{}
+ response.Params.Data = &tradeList
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ if len(tradeList) == 0 {
+ return fmt.Errorf("%v, empty list of trades found", common.ErrNoResponse)
+ }
+ if a == asset.Empty && currencyPair.IsEmpty() {
+ currencyPair, err = currency.NewPairFromString(tradeList[0].InstrumentName)
+ if err != nil {
+ return err
+ }
+ a, err = guessAssetTypeFromInstrument(currencyPair)
+ if err != nil {
+ return err
+ }
+ }
+ tradeDatas := make([]trade.Data, len(tradeList))
+ for x := range tradeDatas {
+ side, err := order.StringToOrderSide(tradeList[x].Direction)
+ if err != nil {
+ return err
+ }
+ currencyPair, err = currency.NewPairFromString(tradeList[x].InstrumentName)
+ if err != nil {
+ return err
+ }
+ tradeDatas[x] = trade.Data{
+ CurrencyPair: currencyPair,
+ Exchange: d.Name,
+ Timestamp: tradeList[x].Timestamp.Time(),
+ Price: tradeList[x].Price,
+ Amount: tradeList[x].Amount,
+ Side: side,
+ TID: tradeList[x].TradeID,
+ AssetType: a,
+ }
+ }
+ return trade.AddTradesToBuffer(d.Name, tradeDatas...)
+}
+
+func (d *Deribit) processIncrementalTicker(respRaw []byte, channels []string) error {
+ if len(channels) != 2 {
+ return fmt.Errorf("%w, expected format 'incremental_ticker.{instrument_name}', but found %s", errMalformedData, strings.Join(channels, "."))
+ }
+ cp, err := currency.NewPairFromString(channels[1])
+ if err != nil {
+ return err
+ }
+ var response WsResponse
+ incrementalTicker := &WsIncrementalTicker{}
+ response.Params.Data = incrementalTicker
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ assetType, err := guessAssetTypeFromInstrument(cp)
+ if err != nil {
+ return err
+ }
+ d.Websocket.DataHandler <- &ticker.Price{
+ ExchangeName: d.Name,
+ Pair: cp,
+ AssetType: assetType,
+ LastUpdated: incrementalTicker.Timestamp.Time(),
+ BidSize: incrementalTicker.BestBidAmount,
+ AskSize: incrementalTicker.BestAskAmount,
+ High: incrementalTicker.MaxPrice,
+ Low: incrementalTicker.MinPrice,
+ Volume: incrementalTicker.Stats.Volume,
+ QuoteVolume: incrementalTicker.Stats.VolumeUsd,
+ Ask: incrementalTicker.ImpliedAsk,
+ Bid: incrementalTicker.ImpliedBid,
+ }
+ return nil
+}
+
+func (d *Deribit) processInstrumentTicker(respRaw []byte, channels []string) error {
+ if len(channels) != 3 {
+ return fmt.Errorf("%w, expected format 'ticker.{instrument_name}.{interval}', but found %s", errMalformedData, strings.Join(channels, "."))
+ }
+ return d.processTicker(respRaw, channels)
+}
+
+func (d *Deribit) processTicker(respRaw []byte, channels []string) error {
+ cp, err := currency.NewPairFromString(channels[1])
+ if err != nil {
+ return err
+ }
+ var a asset.Item
+ var response WsResponse
+ tickerPriceResponse := &wsTicker{}
+ response.Params.Data = tickerPriceResponse
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ a, err = guessAssetTypeFromInstrument(cp)
+ if err != nil {
+ return err
+ }
+ tickerPrice := &ticker.Price{
+ ExchangeName: d.Name,
+ Pair: cp,
+ AssetType: a,
+ LastUpdated: tickerPriceResponse.Timestamp.Time(),
+ Bid: tickerPriceResponse.BestBidPrice,
+ Ask: tickerPriceResponse.BestAskPrice,
+ BidSize: tickerPriceResponse.BestBidAmount,
+ AskSize: tickerPriceResponse.BestAskAmount,
+ Last: tickerPriceResponse.LastPrice,
+ High: tickerPriceResponse.Stats.High,
+ Low: tickerPriceResponse.Stats.Low,
+ Volume: tickerPriceResponse.Stats.Volume,
+ }
+ if a != asset.Futures {
+ tickerPrice.Low = tickerPriceResponse.MinPrice
+ tickerPrice.High = tickerPriceResponse.MaxPrice
+ tickerPrice.Last = tickerPriceResponse.MarkPrice
+ tickerPrice.Ask = tickerPriceResponse.ImpliedAsk
+ tickerPrice.Bid = tickerPriceResponse.ImpliedBid
+ }
+ d.Websocket.DataHandler <- tickerPrice
+ return nil
+}
+
+func (d *Deribit) processData(respRaw []byte, result interface{}) error {
+ var response WsResponse
+ response.Params.Data = result
+ err := json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ d.Websocket.DataHandler <- result
+ return nil
+}
+
+func (d *Deribit) processCandleChart(respRaw []byte, channels []string) error {
+ if len(channels) != 4 {
+ return fmt.Errorf("%w, expected format 'chart.trades.{instrument_name}.{resolution}', but found %s", errMalformedData, strings.Join(channels, "."))
+ }
+ cp, err := currency.NewPairFromString(channels[2])
+ if err != nil {
+ return err
+ }
+ var response WsResponse
+ var a asset.Item
+ candleData := &wsCandlestickData{}
+ response.Params.Data = candleData
+ err = json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ a, err = guessAssetTypeFromInstrument(cp)
+ if err != nil {
+ return err
+ }
+ d.Websocket.DataHandler <- stream.KlineData{
+ Timestamp: time.UnixMilli(candleData.Tick),
+ Pair: cp,
+ AssetType: a,
+ Exchange: d.Name,
+ OpenPrice: candleData.Open,
+ HighPrice: candleData.High,
+ LowPrice: candleData.Low,
+ ClosePrice: candleData.Close,
+ Volume: candleData.Volume,
+ }
+ return nil
+}
+
+func (d *Deribit) processOrderbook(respRaw []byte, channels []string) error {
+ var response WsResponse
+ orderbookData := &wsOrderbook{}
+ response.Params.Data = orderbookData
+ err := json.Unmarshal(respRaw, &response)
+ if err != nil {
+ return err
+ }
+ var assetType asset.Item
+ if len(channels) == 3 {
+ cp, err := currency.NewPairFromString(orderbookData.InstrumentName)
+ if err != nil {
+ return err
+ }
+ asks := make(orderbook.Tranches, 0, len(orderbookData.Asks))
+ for x := range orderbookData.Asks {
+ if len(orderbookData.Asks[x]) != 3 {
+ return errMalformedData
+ }
+ price, okay := orderbookData.Asks[x][1].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid orderbook price", errMalformedData)
+ }
+ amount, okay := orderbookData.Asks[x][2].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid amount", errMalformedData)
+ }
+ asks = append(asks, orderbook.Tranche{
+ Price: price,
+ Amount: amount,
+ })
+ }
+ bids := make(orderbook.Tranches, 0, len(orderbookData.Bids))
+ for x := range orderbookData.Bids {
+ if len(orderbookData.Bids[x]) != 3 {
+ return errMalformedData
+ }
+ price, okay := orderbookData.Bids[x][1].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid orderbook price", errMalformedData)
+ } else if price == 0.0 {
+ continue
+ }
+ amount, okay := orderbookData.Bids[x][2].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid amount", errMalformedData)
+ }
+ bids = append(bids, orderbook.Tranche{
+ Price: price,
+ Amount: amount,
+ })
+ }
+ if len(asks) == 0 && len(bids) == 0 {
+ return nil
+ }
+ assetType, err = guessAssetTypeFromInstrument(cp)
+ if err != nil {
+ return err
+ }
+ if orderbookData.Type == "snapshot" {
+ return d.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
+ Exchange: d.Name,
+ VerifyOrderbook: d.CanVerifyOrderbook,
+ LastUpdated: orderbookData.Timestamp.Time(),
+ Pair: cp,
+ Asks: asks,
+ Bids: bids,
+ Asset: assetType,
+ LastUpdateID: orderbookData.ChangeID,
+ })
+ } else if orderbookData.Type == "change" {
+ return d.Websocket.Orderbook.Update(&orderbook.Update{
+ Asks: asks,
+ Bids: bids,
+ Pair: cp,
+ Asset: assetType,
+ UpdateID: orderbookData.ChangeID,
+ UpdateTime: orderbookData.Timestamp.Time(),
+ })
+ }
+ } else if len(channels) == 5 {
+ cp, err := currency.NewPairFromString(orderbookData.InstrumentName)
+ if err != nil {
+ return err
+ }
+ assetType, err = guessAssetTypeFromInstrument(cp)
+ if err != nil {
+ return err
+ }
+ asks := make(orderbook.Tranches, 0, len(orderbookData.Asks))
+ for x := range orderbookData.Asks {
+ if len(orderbookData.Asks[x]) != 2 {
+ return errMalformedData
+ }
+ price, okay := orderbookData.Asks[x][0].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid orderbook price", errMalformedData)
+ } else if price == 0 {
+ continue
+ }
+ amount, okay := orderbookData.Asks[x][1].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid amount", errMalformedData)
+ }
+ asks = append(asks, orderbook.Tranche{
+ Price: price,
+ Amount: amount,
+ })
+ }
+ bids := make([]orderbook.Tranche, 0, len(orderbookData.Bids))
+ for x := range orderbookData.Bids {
+ if len(orderbookData.Bids[x]) != 2 {
+ return errMalformedData
+ }
+ price, okay := orderbookData.Bids[x][0].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid orderbook price", errMalformedData)
+ } else if price == 0 {
+ continue
+ }
+ amount, okay := orderbookData.Bids[x][1].(float64)
+ if !okay {
+ return fmt.Errorf("%w, invalid amount", errMalformedData)
+ }
+ bids = append(bids, orderbook.Tranche{
+ Price: price,
+ Amount: amount,
+ })
+ }
+ if len(asks) == 0 && len(bids) == 0 {
+ return nil
+ }
+ return d.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
+ Asks: asks,
+ Bids: bids,
+ Pair: cp,
+ Asset: assetType,
+ Exchange: d.Name,
+ LastUpdateID: orderbookData.ChangeID,
+ LastUpdated: orderbookData.Timestamp.Time(),
+ })
+ }
+ return nil
+}
+
+// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions()
+func (d *Deribit) GenerateDefaultSubscriptions() ([]subscription.Subscription, error) {
+ var subscriptions []subscription.Subscription
+ assets := d.GetAssetTypes(true)
+ subscriptionChannels := defaultSubscriptions
+ if d.Websocket.CanUseAuthenticatedEndpoints() {
+ subscriptionChannels = append(
+ subscriptionChannels,
+
+ // authenticated subscriptions
+ rawUsersOrdersKindCurrencyChannel,
+ rawUsersOrdersWithKindCurrencyAndIntervalChannel,
+ userTradesByKindCurrencyAndIntervalChannel,
+ )
+ }
+ var err error
+ assetPairs := make(map[asset.Item][]currency.Pair, len(assets))
+ for _, a := range assets {
+ assetPairs[a], err = d.GetEnabledPairs(a)
+ if err != nil {
+ return nil, err
+ }
+ if len(assetPairs[a]) > 5 {
+ assetPairs[a] = assetPairs[a][:5]
+ }
+ }
+ for x := range subscriptionChannels {
+ switch subscriptionChannels[x] {
+ case chartTradesChannel:
+ for _, a := range assets {
+ for z := range assetPairs[a] {
+ if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" ||
+ !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
+ a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
+ continue
+ }
+ subscriptions = append(subscriptions,
+ subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: assetPairs[a][z],
+ Params: map[string]interface{}{
+ "resolution": "1D",
+ },
+ Asset: a,
+ })
+ }
+ }
+ case incrementalTickerChannel,
+ quoteChannel,
+ rawUserOrdersChannel:
+ for _, a := range assets {
+ for z := range assetPairs[a] {
+ if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" ||
+ !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
+ a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
+ continue
+ }
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: assetPairs[a][z],
+ Asset: a,
+ })
+ }
+ }
+ case orderbookChannel:
+ for _, a := range assets {
+ for z := range assetPairs[a] {
+ if ((assetPairs[a][z].Quote.Upper().String() == "PERPETUAL" ||
+ !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
+ a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
+ continue
+ }
+ subscriptions = append(subscriptions,
+ subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: assetPairs[a][z],
+ // if needed, group and depth of orderbook can be passed as follow "group": "250", "depth": "20",
+ Interval: kline.HundredMilliseconds,
+ Asset: a,
+ Params: map[string]interface{}{
+ "group": "none",
+ "depth": "10",
+ },
+ },
+ )
+ if d.Websocket.CanUseAuthenticatedEndpoints() {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Channel: orderbookChannel,
+ Pair: assetPairs[a][z],
+ Asset: a,
+ Interval: kline.Interval(0),
+ Params: map[string]interface{}{
+ "group": "none",
+ "depth": "10",
+ },
+ })
+ }
+ }
+ }
+ case tickerChannel,
+ tradesChannel:
+ for _, a := range assets {
+ for z := range assetPairs[a] {
+ if ((assetPairs[a][z].Quote.Upper().String() != "PERPETUAL" &&
+ !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL")) &&
+ a == asset.Futures) || (a != asset.Spot && a != asset.Futures) {
+ continue
+ }
+ subscriptions = append(subscriptions,
+ subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: assetPairs[a][z],
+ Interval: kline.HundredMilliseconds,
+ Asset: a,
+ })
+ }
+ }
+ case perpetualChannel,
+ userChangesInstrumentsChannel,
+ userTradesChannelByInstrument:
+ for _, a := range assets {
+ for z := range assetPairs[a] {
+ if subscriptionChannels[x] == perpetualChannel && !strings.Contains(assetPairs[a][z].Quote.Upper().String(), "PERPETUAL") {
+ continue
+ }
+ subscriptions = append(subscriptions,
+ subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: assetPairs[a][z],
+ Interval: kline.HundredMilliseconds,
+ Asset: a,
+ })
+ }
+ }
+ case instrumentStateChannel,
+ rawUsersOrdersKindCurrencyChannel:
+ var okay bool
+ for _, a := range assets {
+ currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a]))
+ for z := range assetPairs[a] {
+ if okay = currencyPairsName[assetPairs[a][z].Base]; !okay {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Asset: a,
+ Channel: subscriptionChannels[x],
+ Pair: currency.Pair{Base: assetPairs[a][z].Base},
+ })
+ currencyPairsName[assetPairs[a][z].Base] = true
+ }
+ if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Asset: a,
+ Channel: subscriptionChannels[x],
+ Pair: currency.Pair{Base: assetPairs[a][z].Quote},
+ })
+ currencyPairsName[assetPairs[a][z].Quote] = true
+ }
+ }
+ }
+ case userChangesCurrencyChannel,
+ userOrdersWithIntervalChannel,
+ rawUsersOrdersWithKindCurrencyAndIntervalChannel,
+ userTradesByKindCurrencyAndIntervalChannel,
+ tradesWithKindChannel:
+ for _, a := range assets {
+ currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a]))
+ var okay bool
+ for z := range assetPairs[a] {
+ if okay = currencyPairsName[assetPairs[a][z].Base]; !okay {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Asset: a,
+ Channel: subscriptionChannels[x],
+ Pair: currency.Pair{Base: assetPairs[a][z].Base},
+ Interval: kline.HundredMilliseconds,
+ })
+ currencyPairsName[assetPairs[a][z].Base] = true
+ }
+ if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Asset: a,
+ Channel: subscriptionChannels[x],
+ Pair: currency.Pair{Base: assetPairs[a][z].Quote},
+ Interval: kline.HundredMilliseconds,
+ })
+ currencyPairsName[assetPairs[a][z].Quote] = true
+ }
+ }
+ }
+ case requestForQuoteChannel,
+ userMMPTriggerChannel,
+ userPortfolioChannel:
+ for _, a := range assets {
+ currencyPairsName := make(map[currency.Code]bool, 2*len(assetPairs[a]))
+ var okay bool
+ for z := range assetPairs[a] {
+ if okay = currencyPairsName[assetPairs[a][z].Base]; !okay {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: currency.Pair{Base: assetPairs[a][z].Base},
+ Asset: a,
+ })
+ currencyPairsName[assetPairs[a][z].Base] = true
+ }
+ if okay = currencyPairsName[assetPairs[a][z].Quote]; !okay {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Pair: currency.Pair{Base: assetPairs[a][z].Quote},
+ Asset: a,
+ })
+ currencyPairsName[assetPairs[a][z].Quote] = true
+ }
+ }
+ }
+ case announcementsChannel,
+ userAccessLogChannel,
+ platformStateChannel,
+ userLockChannel,
+ platformStatePublicMethodsStateChannel:
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ })
+ case priceIndexChannel,
+ priceRankingChannel,
+ priceStatisticsChannel,
+ volatilityIndexChannel,
+ markPriceOptionsChannel,
+ estimatedExpirationPriceChannel:
+ for i := range indexENUMS {
+ subscriptions = append(subscriptions, subscription.Subscription{
+ Channel: subscriptionChannels[x],
+ Params: map[string]interface{}{
+ "index_name": indexENUMS[i],
+ },
+ })
+ }
+ }
+ }
+ return subscriptions, nil
+}
+
+func (d *Deribit) generatePayloadFromSubscriptionInfos(operation string, subscs []subscription.Subscription) ([]WsSubscriptionInput, error) {
+ subscriptionPayloads := make([]WsSubscriptionInput, len(subscs))
+ for x := range subscs {
+ sub := WsSubscriptionInput{
+ JSONRPCVersion: rpcVersion,
+ ID: d.Websocket.Conn.GenerateMessageID(false),
+ Method: "public/" + operation,
+ Params: map[string][]string{},
+ }
+ switch subscs[x].Channel {
+ case userAccessLogChannel, userChangesInstrumentsChannel, userChangesCurrencyChannel, userLockChannel, userMMPTriggerChannel, rawUserOrdersChannel,
+ userOrdersWithIntervalChannel, rawUsersOrdersKindCurrencyChannel, userPortfolioChannel, userTradesChannelByInstrument, userTradesByKindCurrencyAndIntervalChannel:
+ if !d.Websocket.CanUseAuthenticatedEndpoints() {
+ continue
+ }
+ sub.Method = "private/" + operation
+ }
+ var instrumentID string
+ if !subscs[x].Pair.IsEmpty() {
+ pairFormat, err := d.GetPairFormat(subscs[x].Asset, true)
+ if err != nil {
+ return nil, err
+ }
+ subscs[x].Pair = subscs[x].Pair.Format(pairFormat)
+ if subscs[x].Asset == asset.Futures {
+ instrumentID = d.formatFuturesTradablePair(subscs[x].Pair.Format(pairFormat))
+ } else {
+ instrumentID = subscs[x].Pair.String()
+ }
+ }
+ switch subscs[x].Channel {
+ case announcementsChannel,
+ userAccessLogChannel,
+ platformStateChannel,
+ platformStatePublicMethodsStateChannel,
+ userLockChannel:
+ sub.Params["channels"] = []string{subscs[x].Channel}
+ case orderbookChannel:
+ if subscs[x].Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+ intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval)
+ if err != nil {
+ return nil, err
+ }
+ group, okay := subscs[x].Params["group"].(string)
+ if !okay {
+ sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + intervalString}
+ break
+ }
+ depth, okay := subscs[x].Params["depth"].(string)
+ if !okay {
+ sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + intervalString}
+ break
+ }
+ sub.Params["channels"] = []string{orderbookChannel + "." + instrumentID + "." + group + "." + depth + "." + intervalString}
+ case chartTradesChannel:
+ if subscs[x].Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+ resolution, okay := subscs[x].Params["resolution"].(string)
+ if !okay {
+ resolution = "1D"
+ }
+ sub.Params["channels"] = []string{chartTradesChannel + "." + d.formatFuturesTradablePair(subscs[x].Pair) + "." + resolution}
+ case priceIndexChannel,
+ priceRankingChannel,
+ priceStatisticsChannel,
+ volatilityIndexChannel,
+ markPriceOptionsChannel,
+ estimatedExpirationPriceChannel:
+ indexName, okay := subscs[x].Params["index_name"].(string)
+ if !okay {
+ return nil, errUnsupportedIndexName
+ }
+ sub.Params["channels"] = []string{subscs[x].Channel + "." + indexName}
+ case instrumentStateChannel:
+ kind := d.GetAssetKind(subscs[x].Asset)
+ currencyCode := getValidatedCurrencyCode(subscs[x].Pair)
+ sub.Params["channels"] = []string{"instrument.state." + kind + "." + currencyCode}
+ case rawUsersOrdersKindCurrencyChannel:
+ kind := d.GetAssetKind(subscs[x].Asset)
+ currencyCode := getValidatedCurrencyCode(subscs[x].Pair)
+ sub.Params["channels"] = []string{"user.orders." + kind + "." + currencyCode + ".raw"}
+ case quoteChannel,
+ incrementalTickerChannel:
+ if subscs[x].Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+ sub.Params["channels"] = []string{subscs[x].Channel + "." + instrumentID}
+ case rawUserOrdersChannel:
+ if subscs[x].Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+ sub.Params["channels"] = []string{"user.orders." + instrumentID + ".raw"}
+ case requestForQuoteChannel,
+ userMMPTriggerChannel,
+ userPortfolioChannel:
+ currencyCode := getValidatedCurrencyCode(subscs[x].Pair)
+ sub.Params["channels"] = []string{subscs[x].Channel + "." + currencyCode}
+ case tradesChannel,
+ userChangesInstrumentsChannel,
+ userOrdersWithIntervalChannel,
+ tickerChannel,
+ perpetualChannel,
+ userTradesChannelByInstrument:
+ if subscs[x].Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+ if subscs[x].Interval.Duration() == 0 {
+ sub.Params["channels"] = []string{subscs[x].Channel + instrumentID}
+ continue
+ }
+ intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval)
+ if err != nil {
+ return nil, err
+ }
+ sub.Params["channels"] = []string{subscs[x].Channel + instrumentID + "." + intervalString}
+ case userChangesCurrencyChannel,
+ tradesWithKindChannel,
+ rawUsersOrdersWithKindCurrencyAndIntervalChannel,
+ userTradesByKindCurrencyAndIntervalChannel:
+ kind := d.GetAssetKind(subscs[x].Asset)
+ currencyCode := getValidatedCurrencyCode(subscs[x].Pair)
+ if subscs[x].Interval.Duration() == 0 {
+ sub.Params["channels"] = []string{subscs[x].Channel + "." + kind + "." + currencyCode}
+ continue
+ }
+ intervalString, err := d.GetResolutionFromInterval(subscs[x].Interval)
+ if err != nil {
+ return nil, err
+ }
+ sub.Params["channels"] = []string{subscs[x].Channel + "." + kind + "." + currencyCode + "." + intervalString}
+ default:
+ return nil, errUnsupportedChannel
+ }
+ subscriptionPayloads[x] = sub
+ }
+ return filterSubscriptionPayloads(subscriptionPayloads), nil
+}
+
+// Subscribe sends a websocket message to receive data from the channel
+func (d *Deribit) Subscribe(channelsToSubscribe []subscription.Subscription) error {
+ return d.handleSubscription("subscribe", channelsToSubscribe)
+}
+
+// Unsubscribe sends a websocket message to stop receiving data from the channel
+func (d *Deribit) Unsubscribe(channelsToUnsubscribe []subscription.Subscription) error {
+ return d.handleSubscription("unsubscribe", channelsToUnsubscribe)
+}
+
+func filterSubscriptionPayloads(subscription []WsSubscriptionInput) []WsSubscriptionInput {
+ newSubscriptionsMap := map[string]bool{}
+ newSubscs := make([]WsSubscriptionInput, 0, len(subscription))
+ for x := range subscription {
+ if len(subscription[x].Params["channels"]) == 0 {
+ continue
+ }
+ if !newSubscriptionsMap[subscription[x].Params["channels"][0]] {
+ newSubscriptionsMap[subscription[x].Params["channels"][0]] = true
+ newSubscs = append(newSubscs, subscription[x])
+ }
+ }
+ return newSubscs
+}
+
+func (d *Deribit) handleSubscription(operation string, channels []subscription.Subscription) error {
+ payloads, err := d.generatePayloadFromSubscriptionInfos(operation, channels)
+ if err != nil {
+ return err
+ }
+ for x := range payloads {
+ data, err := d.Websocket.Conn.SendMessageReturnResponse(payloads[x].ID, payloads[x])
+ if err != nil {
+ return err
+ }
+ var response wsSubscriptionResponse
+ err = json.Unmarshal(data, &response)
+ if err != nil {
+ return fmt.Errorf("%v %v", d.Name, err)
+ }
+ if payloads[x].ID == response.ID && len(response.Result) == 0 {
+ log.Errorf(log.ExchangeSys, "subscription to channel %s was not successful", payloads[x].Params["channels"][0])
+ }
+ }
+ return nil
+}
+
+func getValidatedCurrencyCode(pair currency.Pair) string {
+ currencyCode := pair.Base.Upper().String()
+ switch currencyCode {
+ case currencyBTC, currencyETH,
+ currencySOL, currencyUSDT,
+ currencyUSDC, currencyEURR:
+ return currencyCode
+ default:
+ switch {
+ case strings.Contains(pair.String(), currencyUSDC):
+ return currencyUSDC
+ case strings.Contains(pair.String(), currencyUSDT):
+ return currencyUSDT
+ }
+ return "any"
+ }
+}
diff --git a/exchanges/deribit/deribit_websocket_eps.go b/exchanges/deribit/deribit_websocket_eps.go
new file mode 100644
index 00000000..19f3010c
--- /dev/null
+++ b/exchanges/deribit/deribit_websocket_eps.go
@@ -0,0 +1,2442 @@
+package deribit
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/currency"
+ "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/log"
+)
+
+// WSRetrieveBookBySummary retrieves book summary data for currency requested through websocket connection.
+func (d *Deribit) WSRetrieveBookBySummary(ccy currency.Code, kind string) ([]BookSummaryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ }{
+ Currency: ccy,
+ }
+ if kind != "" {
+ input.Kind = kind
+ }
+ var resp []BookSummaryData
+ return resp, d.SendWSRequest(nonMatchingEPL, getBookByCurrency, input, &resp, false)
+}
+
+// WSRetrieveBookSummaryByInstrument retrieves book summary data for instrument requested through the websocket connection.
+func (d *Deribit) WSRetrieveBookSummaryByInstrument(instrument string) ([]BookSummaryData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name,omitempty"`
+ }{
+ Instrument: instrument,
+ }
+ var resp []BookSummaryData
+ return resp, d.SendWSRequest(nonMatchingEPL, getBookByInstrument, input, &resp, false)
+}
+
+// WSRetrieveContractSize retrieves contract size for instrument requested through the websocket connection.
+func (d *Deribit) WSRetrieveContractSize(instrument string) (*ContractSizeData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ }{
+ Instrument: instrument,
+ }
+ var resp *ContractSizeData
+ return resp, d.SendWSRequest(nonMatchingEPL, getContractSize, input, &resp, false)
+}
+
+// WSRetrieveCurrencies retrieves all cryptocurrencies supported by the API through the websocket connection.
+func (d *Deribit) WSRetrieveCurrencies() ([]CurrencyData, error) {
+ var resp []CurrencyData
+ return resp, d.SendWSRequest(nonMatchingEPL, getCurrencies, nil, &resp, false)
+}
+
+// WSRetrieveDeliveryPrices retrieves delivery prices using index name through the websocket connection.
+func (d *Deribit) WSRetrieveDeliveryPrices(indexName string, offset, count int64) (*IndexDeliveryPrice, error) {
+ if indexName == "" {
+ return nil, errUnsupportedIndexName
+ }
+ input := &struct {
+ IndexName string `json:"index_name"`
+ Offset int64 `json:"offset,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ }{
+ IndexName: indexName,
+ Offset: offset,
+ Count: count,
+ }
+ var resp *IndexDeliveryPrice
+ return resp, d.SendWSRequest(nonMatchingEPL, getDeliveryPrices, input, &resp, false)
+}
+
+// WSRetrieveFundingChartData retrieves funding chart data for the requested instrument and time length through the websocket connection.
+// supported lengths: 8h, 24h, 1m <-(1month)
+func (d *Deribit) WSRetrieveFundingChartData(instrument, length string) (*FundingChartData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ if length == "" {
+ return nil, errIntervalNotSupported
+ }
+ input := &struct {
+ InstrumentName string `json:"instrument_name"`
+ Length string `json:"length"`
+ }{
+ InstrumentName: instrument,
+ Length: length,
+ }
+ var resp *FundingChartData
+ return resp, d.SendWSRequest(nonMatchingEPL, getFundingChartData, input, &resp, false)
+}
+
+// WSRetrieveFundingRateHistory retrieves hourly historical interest rate for requested PERPETUAL instrument through the websocket connection.
+func (d *Deribit) WSRetrieveFundingRateHistory(instrumentName string, startTime, endTime time.Time) ([]FundingRateHistory, error) {
+ if instrumentName == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ err := common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ input := &struct {
+ InstrumentName string `json:"instrument_name"`
+ StartTime int64 `json:"start_timestamp"`
+ EndTime int64 `json:"end_timestamp"`
+ }{
+ InstrumentName: instrumentName,
+ StartTime: startTime.UnixMilli(),
+ EndTime: endTime.UnixMilli(),
+ }
+ var resp []FundingRateHistory
+ return resp, d.SendWSRequest(nonMatchingEPL, getFundingRateHistory, input, &resp, false)
+}
+
+// WSRetrieveFundingRateValue retrieves funding rate value data through the websocket connection.
+func (d *Deribit) WSRetrieveFundingRateValue(instrument string, startTime, endTime time.Time) (float64, error) {
+ if instrument == "" {
+ return 0, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return 0, err
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ StartTimestamp int64 `json:"start_timestamp"`
+ EndTimestamp int64 `json:"end_timestamp"`
+ }{
+ Instrument: instrument,
+ StartTimestamp: startTime.UnixMilli(),
+ EndTimestamp: endTime.UnixMilli(),
+ }
+ var resp float64
+ return resp, d.SendWSRequest(nonMatchingEPL, getFundingRateValue, input, &resp, false)
+}
+
+// WSRetrieveHistoricalVolatility retrieves historical volatility data
+func (d *Deribit) WSRetrieveHistoricalVolatility(ccy currency.Code) ([]HistoricalVolatilityData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ }{
+ Currency: ccy,
+ }
+ var data [][2]interface{}
+ err := d.SendWSRequest(nonMatchingEPL, getHistoricalVolatility, input, &data, false)
+ if err != nil {
+ return nil, err
+ }
+ resp := make([]HistoricalVolatilityData, len(data))
+ for x := range data {
+ timeData, ok := data[x][0].(float64)
+ if !ok {
+ return resp, common.GetTypeAssertError("float64", data[x][0], "time data")
+ }
+ val, ok := data[x][1].(float64)
+ if !ok {
+ return resp, common.GetTypeAssertError("float64", data[x][1], "volatility value")
+ }
+ resp[x] = HistoricalVolatilityData{
+ Timestamp: timeData,
+ Value: val,
+ }
+ }
+ return resp, nil
+}
+
+// WSRetrieveCurrencyIndexPrice the current index price for the instruments, for the selected currency through the websocket connection.
+func (d *Deribit) WSRetrieveCurrencyIndexPrice(ccy currency.Code) (map[string]float64, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ }{
+ Currency: ccy,
+ }
+ var resp map[string]float64
+ return resp, d.SendWSRequest(nonMatchingEPL, getCurrencyIndexPrice, input, &resp, false)
+}
+
+// WSRetrieveIndexPrice retrieves price data for the requested index through the websocket connection.
+func (d *Deribit) WSRetrieveIndexPrice(index string) (*IndexPriceData, error) {
+ if index == "" {
+ return nil, fmt.Errorf("%w index can not be empty", errUnsupportedIndexName)
+ }
+ input := &struct {
+ IndexName string `json:"index_name"`
+ }{
+ IndexName: index,
+ }
+ var resp *IndexPriceData
+ return resp, d.SendWSRequest(nonMatchingEPL, getIndexPrice, input, &resp, false)
+}
+
+// WSRetrieveIndexPriceNames names of indexes through the websocket connection.
+func (d *Deribit) WSRetrieveIndexPriceNames() ([]string, error) {
+ var resp []string
+ return resp, d.SendWSRequest(nonMatchingEPL, getIndexPriceNames, nil, &resp, false)
+}
+
+// WSRetrieveInstrumentData retrieves data for a requested instrument through the websocket connection.
+func (d *Deribit) WSRetrieveInstrumentData(instrument string) (*InstrumentData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ }{
+ Instrument: instrument,
+ }
+ var resp *InstrumentData
+ return resp, d.SendWSRequest(nonMatchingEPL, getInstrument, input, &resp, false)
+}
+
+// WSRetrieveInstrumentsData gets data for all available instruments
+func (d *Deribit) WSRetrieveInstrumentsData(ccy currency.Code, kind string, expired bool) ([]InstrumentData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Expired bool `json:"expired"`
+ Kind string `json:"kind,omitempty"`
+ }{
+ Currency: ccy,
+ Expired: expired,
+ Kind: kind,
+ }
+ var resp []InstrumentData
+ return resp, d.SendWSRequest(nonMatchingEPL, getInstruments, input, &resp, false)
+}
+
+// WSRetrieveLastSettlementsByCurrency retrieves last settlement data by currency through the websocket connection.
+func (d *Deribit) WSRetrieveLastSettlementsByCurrency(ccy currency.Code, settlementType, continuation string, count int64, startTime time.Time) (*SettlementsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency string `json:"currency,omitempty"`
+ Type string `json:"type,omitempty"`
+ Continuation string `json:"continuation,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ SearchStartTimestamp int64 `json:"search_start_timestamp,omitempty"`
+ }{
+ Currency: ccy.String(),
+ Type: settlementType,
+ Continuation: continuation,
+ Count: count,
+ SearchStartTimestamp: startTime.UnixMilli(),
+ }
+ var resp *SettlementsData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastSettlementsByCurrency, input, &resp, false)
+}
+
+// WSRetrieveLastSettlementsByInstrument retrieves last settlement data for requested instrument through the websocket connection.
+func (d *Deribit) WSRetrieveLastSettlementsByInstrument(instrument, settlementType, continuation string, count int64, startTime time.Time) (*SettlementsData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ SettlementType string `json:"type,omitempty"`
+ Continuation string `json:"continuation,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ SearchStartTimestamp int64 `json:"search_start_timestamp,omitempty"`
+ }{
+ Instrument: instrument,
+ SettlementType: settlementType,
+ Continuation: continuation,
+ Count: count,
+ }
+ if !startTime.IsZero() {
+ input.SearchStartTimestamp = startTime.UnixMilli()
+ }
+ var resp *SettlementsData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastSettlementsByInstrument, input, &resp, false)
+}
+
+// WSRetrieveLastTradesByCurrency retrieves last trades for requested currency through the websocket connection.
+func (d *Deribit) WSRetrieveLastTradesByCurrency(ccy currency.Code, kind, startID, endID, sorting string, count int64, includeOld bool) (*PublicTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ StartID string `json:"start_id,omitempty"`
+ EndID string `json:"end_id,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ StartID: startID,
+ EndID: endID,
+ Count: count,
+ IncludeOld: includeOld,
+ Sorting: sorting,
+ }
+ var resp *PublicTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastTradesByCurrency, input, &resp, false)
+}
+
+// WSRetrieveLastTradesByCurrencyAndTime retrieves last trades for requested currency and time intervals through the websocket connection.
+func (d *Deribit) WSRetrieveLastTradesByCurrencyAndTime(ccy currency.Code, kind, sorting string, count int64, includeOld bool, startTime, endTime time.Time) (*PublicTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ StartTimestamp int64 `json:"start_timestamp,omitempty"`
+ EndTimestamp int64 `json:"end_timestamp,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ Count: count,
+ StartTimestamp: startTime.UnixMilli(),
+ EndTimestamp: endTime.UnixMilli(),
+ IncludeOld: includeOld,
+ Sorting: sorting,
+ }
+ var resp *PublicTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastTradesByCurrencyAndTime, input, &resp, false)
+}
+
+// WSRetrieveLastTradesByInstrument retrieves last trades for requested instrument requested through the websocket connection.
+func (d *Deribit) WSRetrieveLastTradesByInstrument(instrument, startSeq, endSeq, sorting string, count int64, includeOld bool) (*PublicTradesData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name,omitempty"`
+ StartSequence string `json:"start_seq,omitempty"`
+ EndSequence string `json:"end_seq,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ }{
+ Instrument: instrument,
+ StartSequence: startSeq,
+ EndSequence: endSeq,
+ Sorting: sorting,
+ Count: count,
+ IncludeOld: includeOld,
+ }
+ var resp *PublicTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastTradesByInstrument, input, &resp, false)
+}
+
+// WSRetrieveLastTradesByInstrumentAndTime retrieves last trades for requested instrument requested and time intervals through the websocket connection.
+func (d *Deribit) WSRetrieveLastTradesByInstrumentAndTime(instrument, sorting string, count int64, includeOld bool, startTime, endTime time.Time) (*PublicTradesData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name,omitempty"`
+ StartTimestamp int64 `json:"start_timestamp,omitempty"`
+ EndTimestamp int64 `json:"end_timestamp,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ }{
+ Instrument: instrument,
+ Sorting: sorting,
+ Count: count,
+ IncludeOld: includeOld,
+ }
+ input.StartTimestamp = startTime.UnixMilli()
+ input.EndTimestamp = endTime.UnixMilli()
+ var resp *PublicTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastTradesByInstrumentAndTime, input, &resp, false)
+}
+
+// WSRetrieveMarkPriceHistory retrieves data for mark price history through the websocket connection.
+func (d *Deribit) WSRetrieveMarkPriceHistory(instrument string, startTime, endTime time.Time) ([]MarkPriceHistory, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name,omitempty"`
+ StartTimestamp int64 `json:"start_timestamp,omitempty"`
+ EndTimestamp int64 `json:"end_timestamp,omitempty"`
+ }{
+ Instrument: instrument,
+ StartTimestamp: startTime.UnixMilli(),
+ EndTimestamp: endTime.UnixMilli(),
+ }
+ var resp []MarkPriceHistory
+ return resp, d.SendWSRequest(nonMatchingEPL, getMarkPriceHistory, input, &resp, false)
+}
+
+// WSRetrieveOrderbookData retrieves data orderbook of requested instrument through the web-socket connection.
+func (d *Deribit) WSRetrieveOrderbookData(instrument string, depth int64) (*Orderbook, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Depth int64 `json:"depth,omitempty"`
+ }{
+ Instrument: instrument,
+ Depth: depth,
+ }
+ var resp *Orderbook
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderbook, input, &resp, false)
+}
+
+// WSRetrieveOrderbookByInstrumentID retrieves orderbook by instrument ID through websocket connection.
+func (d *Deribit) WSRetrieveOrderbookByInstrumentID(instrumentID int64, depth float64) (*Orderbook, error) {
+ if instrumentID == 0 {
+ return nil, errInvalidInstrumentID
+ }
+ input := &struct {
+ InstrumentID int64 `json:"instrument_id"`
+ Depth float64 `json:"depth,omitempty"`
+ }{
+ InstrumentID: instrumentID,
+ Depth: depth,
+ }
+ var resp *Orderbook
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderbookByInstrumentID, input, &resp, false)
+}
+
+// WsRetrieveSupportedIndexNames retrieves the identifiers of all supported Price Indexes
+// 'type' represents Type of a cryptocurrency price index. possible 'all', 'spot', 'derivative'
+func (d *Deribit) WsRetrieveSupportedIndexNames(priceIndexType string) ([]string, error) {
+ input := &struct {
+ PriceIndexType string `json:"type,omitempty"`
+ }{
+ PriceIndexType: priceIndexType,
+ }
+ var resp []string
+ return resp, d.SendWSRequest(nonMatchingEPL, "public/get_supported_index_names", input, &resp, false)
+}
+
+// WSRetrieveRequestForQuote retrieves RFQ information.
+func (d *Deribit) WSRetrieveRequestForQuote(ccy currency.Code, kind string) ([]RequestForQuote, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ }
+ var resp []RequestForQuote
+ return resp, d.SendWSRequest(nonMatchingEPL, getRFQ, input, &resp, false)
+}
+
+// WSRetrieveTradeVolumes retrieves trade volumes' data of all instruments through the websocket connection.
+func (d *Deribit) WSRetrieveTradeVolumes(extended bool) ([]TradeVolumesData, error) {
+ input := &struct {
+ Extended bool `json:"extended,omitempty"`
+ }{
+ Extended: extended,
+ }
+ var resp []TradeVolumesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getTradeVolumes, input, &resp, false)
+}
+
+// WSRetrievesTradingViewChartData retrieves volatility index data for the requested instrument through the websocket connection.
+func (d *Deribit) WSRetrievesTradingViewChartData(instrument, resolution string, startTime, endTime time.Time) (*TVChartData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return nil, err
+ }
+ if resolution == "" {
+ return nil, errors.New("unsupported resolution, resolution can not be empty")
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name,omitempty"`
+ StartTimestamp int64 `json:"start_timestamp,omitempty"`
+ EndTimestamp int64 `json:"end_timestamp,omitempty"`
+ Resolution string `json:"resolution,omitempty"`
+ }{
+ Instrument: instrument,
+ Resolution: resolution,
+ StartTimestamp: startTime.UnixMilli(),
+ EndTimestamp: endTime.UnixMilli(),
+ }
+ var resp *TVChartData
+ return resp, d.SendWSRequest(nonMatchingEPL, getTradingViewChartData, input, &resp, false)
+}
+
+// WSRetrieveVolatilityIndexData retrieves volatility index data for the requested currency through the websocket connection.
+func (d *Deribit) WSRetrieveVolatilityIndexData(ccy currency.Code, resolution string, startTime, endTime time.Time) ([]VolatilityIndexData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if resolution == "" {
+ return nil, errResolutionNotSet
+ }
+ err := common.StartEndTimeCheck(startTime, endTime)
+ if err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Currency string `json:"currency,omitempty"`
+ StartTimestamp int64 `json:"start_timestamp,omitempty"`
+ EndTimestamp int64 `json:"end_timestamp,omitempty"`
+ Resolution string `json:"resolution,omitempty"`
+ }{
+ Currency: ccy.String(),
+ Resolution: resolution,
+ StartTimestamp: startTime.UnixMilli(),
+ EndTimestamp: endTime.UnixMilli(),
+ }
+ var resp VolatilityIndexRawData
+ err = d.SendWSRequest(nonMatchingEPL, getVolatilityIndex, input, &resp, false)
+ if err != nil {
+ return nil, err
+ }
+ response := make([]VolatilityIndexData, len(resp.Data))
+ for x := range resp.Data {
+ response[x] = VolatilityIndexData{
+ TimestampMS: time.UnixMilli(int64(resp.Data[x][0])),
+ Open: resp.Data[x][1],
+ High: resp.Data[x][2],
+ Low: resp.Data[x][3],
+ Close: resp.Data[x][4],
+ }
+ }
+ return response, nil
+}
+
+// WSRetrievePublicTicker retrieves public ticker data of the instrument requested through the websocket connection.
+func (d *Deribit) WSRetrievePublicTicker(instrument string) (*TickerData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name,omitempty"`
+ }{
+ Instrument: instrument,
+ }
+ var resp *TickerData
+ return resp, d.SendWSRequest(nonMatchingEPL, getTicker, input, &resp, false)
+}
+
+// WSRetrieveAccountSummary retrieves account summary data for the requested instrument through the websocket connection.
+func (d *Deribit) WSRetrieveAccountSummary(ccy currency.Code, extended bool) (*AccountSummaryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Extended bool `json:"extended"`
+ }{
+ Currency: ccy,
+ Extended: extended,
+ }
+ var resp *AccountSummaryData
+ return resp, d.SendWSRequest(nonMatchingEPL, getAccountSummary, input, &resp, true)
+}
+
+// WSCancelWithdrawal cancels withdrawal request for a given currency by its id through the websocket connection.
+func (d *Deribit) WSCancelWithdrawal(ccy currency.Code, id int64) (*CancelWithdrawalData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, withdrawal id has to be positive integer", errInvalidID)
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ ID int64 `json:"id"`
+ }{
+ Currency: ccy,
+ ID: id,
+ }
+ var resp *CancelWithdrawalData
+ return resp, d.SendWSRequest(nonMatchingEPL, cancelWithdrawal, input, &resp, true)
+}
+
+// WSCancelTransferByID cancels transfer by ID through the websocket connection.
+func (d *Deribit) WSCancelTransferByID(ccy currency.Code, tfa string, id int64) (*AccountSummaryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, transfer id has to be positive integer", errInvalidID)
+ }
+ input := &struct {
+ Currency string `json:"currency"`
+ TwoFactorAuthenticationCode string `json:"tfa,omitempty"`
+ ID int64 `json:"id"`
+ }{
+ Currency: ccy.String(),
+ ID: id,
+ TwoFactorAuthenticationCode: tfa,
+ }
+ var resp *AccountSummaryData
+ return resp, d.SendWSRequest(nonMatchingEPL, cancelTransferByID, input, &resp, true)
+}
+
+// WSCreateDepositAddress creates a deposit address for the currency requested through the websocket connection.
+func (d *Deribit) WSCreateDepositAddress(ccy currency.Code) (*DepositAddressData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ }{
+ Currency: ccy,
+ }
+ var resp *DepositAddressData
+ return resp, d.SendWSRequest(nonMatchingEPL, createDepositAddress, input, &resp, true)
+}
+
+// WSRetrieveDeposits retrieves the deposits of a given currency through the websocket connection.
+func (d *Deribit) WSRetrieveDeposits(ccy currency.Code, count, offset int64) (*DepositsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Count int64 `json:"count,omitempty"`
+ Offset int64 `json:"offset,omitempty"`
+ }{
+ Currency: ccy,
+ Count: count,
+ Offset: offset,
+ }
+ var resp *DepositsData
+ return resp, d.SendWSRequest(nonMatchingEPL, getDeposits, input, &resp, true)
+}
+
+// WSRetrieveTransfers retrieves data for the requested currency through the websocket connection.
+func (d *Deribit) WSRetrieveTransfers(ccy currency.Code, count, offset int64) (*TransfersData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency string `json:"currency,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ Offset int64 `json:"offset,omitempty"`
+ }{
+ Currency: ccy.String(),
+ Count: count,
+ Offset: offset,
+ }
+ var resp *TransfersData
+ return resp, d.SendWSRequest(nonMatchingEPL, getTransfers, input, &resp, true)
+}
+
+// WSRetrieveCurrentDepositAddress retrieves the current deposit address for the requested currency through the websocket connection.
+func (d *Deribit) WSRetrieveCurrentDepositAddress(ccy currency.Code) (*DepositAddressData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ }{
+ Currency: ccy,
+ }
+ var resp *DepositAddressData
+ err := d.SendWSRequest(nonMatchingEPL, getCurrentDepositAddress, input, &resp, true)
+ if err != nil {
+ return nil, err
+ } else if resp == nil {
+ return nil, common.ErrNoResponse
+ }
+ return resp, nil
+}
+
+// WSRetrieveWithdrawals retrieves withdrawals data for a requested currency through the websocket connection.
+func (d *Deribit) WSRetrieveWithdrawals(ccy currency.Code, count, offset int64) (*WithdrawalsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Count int64 `json:"count,omitempty"`
+ Offset int64 `json:"offset,omitempty"`
+ }{
+ Currency: ccy,
+ Count: count,
+ Offset: offset,
+ }
+ var resp *WithdrawalsData
+ return resp, d.SendWSRequest(nonMatchingEPL, getWithdrawals, input, &resp, true)
+}
+
+// WsSubmitTransferBetweenSubAccounts transfer funds between two (sub)accounts.
+func (d *Deribit) WsSubmitTransferBetweenSubAccounts(ccy currency.Code, amount float64, destinationID int64, source string) (*TransferData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, fmt.Errorf("%w, amount : %f", errInvalidAmount, amount)
+ }
+ if destinationID <= 0 {
+ return nil, errInvalidDestinationID
+ }
+ input := &struct {
+ Currency string `json:"currency"`
+ Amount float64 `json:"amount"`
+ Destination int64 `json:"destination"`
+ Source string `json:"source,omitempty"`
+ }{
+ Currency: ccy.String(),
+ Amount: amount,
+ Destination: destinationID,
+ Source: source,
+ }
+ var resp *TransferData
+ return resp, d.SendWSRequest(nonMatchingEPL, submitTransferBetweenSubAccounts, input, &resp, true)
+}
+
+// WSSubmitTransferToSubAccount submits a request to transfer a currency to a subaccount
+func (d *Deribit) WSSubmitTransferToSubAccount(ccy currency.Code, amount float64, destinationID int64) (*TransferData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if destinationID <= 0 {
+ return nil, errInvalidDestinationID
+ }
+ input := &struct {
+ Currency string `json:"currency"`
+ Destination int64 `json:"destination"`
+ Amount float64 `json:"amount"`
+ }{
+ Currency: ccy.String(),
+ Destination: destinationID,
+ Amount: amount,
+ }
+ var resp *TransferData
+ return resp, d.SendWSRequest(nonMatchingEPL, submitTransferToSubaccount, input, &resp, true)
+}
+
+// WSSubmitTransferToUser submits a request to transfer a currency to another user through the websocket connection.
+func (d *Deribit) WSSubmitTransferToUser(ccy currency.Code, tfa, destinationAddress string, amount float64) (*TransferData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if destinationAddress == "" {
+ return nil, errInvalidCryptoAddress
+ }
+ input := &struct {
+ Currency string `json:"currency"`
+ TwoFactorAuthenticationCode string `json:"tfa,omitempty"`
+ DestinationID string `json:"destination"`
+ Amount float64 `json:"amount"`
+ }{
+ Currency: ccy.String(),
+ TwoFactorAuthenticationCode: tfa,
+ DestinationID: destinationAddress,
+ Amount: amount,
+ }
+ var resp *TransferData
+ return resp, d.SendWSRequest(nonMatchingEPL, submitTransferToUser, input, &resp, true)
+}
+
+// ----------------------------------------------------------------------------
+
+// WSSubmitWithdraw submits a withdrawal request to the exchange for the requested currency through the websocket connection.
+func (d *Deribit) WSSubmitWithdraw(ccy currency.Code, address, priority string, amount float64) (*WithdrawData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if address == "" {
+ return nil, errInvalidCryptoAddress
+ }
+ input := &struct {
+ Currency string `json:"currency"`
+ Address string `json:"address"`
+ Priority string `json:"priority,omitempty"`
+ Amount float64 `json:"amount"`
+ }{
+ Currency: ccy.String(),
+ Address: address,
+ Priority: priority,
+ Amount: amount,
+ }
+ var resp *WithdrawData
+ return resp, d.SendWSRequest(nonMatchingEPL, submitWithdraw, input, &resp, true)
+}
+
+// WSRetrieveAnnouncements retrieves announcements through the websocket connection. Default "start_timestamp" parameter value is current timestamp, "count" parameter value must be between 1 and 50, default is 5.
+func (d *Deribit) WSRetrieveAnnouncements(startTime time.Time, count int64) ([]Announcement, error) {
+ input := &struct {
+ StartTime int64 `json:"start_time,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ }{}
+ if !startTime.IsZero() {
+ input.StartTime = startTime.UnixMilli()
+ }
+ if count > 0 {
+ input.Count = count
+ }
+ var resp []Announcement
+ return resp, d.SendWSRequest(nonMatchingEPL, getAnnouncements, input, &resp, false)
+}
+
+// WSRetrievePublicPortfolioMargins public version of the method calculates portfolio margin info for simulated position. For concrete user position, the private version of the method must be used. The public version of the request has special restricted rate limit (not more than once per a second for the IP).
+func (d *Deribit) WSRetrievePublicPortfolioMargins(ccy currency.Code, simulatedPositions map[string]float64) (*PortfolioMargin, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ SimulatedPositions map[string]float64 `json:"simulated_positions"`
+ }{
+ Currency: ccy,
+ }
+ if len(simulatedPositions) != 0 {
+ input.SimulatedPositions = simulatedPositions
+ }
+ var resp *PortfolioMargin
+ return resp, d.SendWSRequest(nonMatchingEPL, getPublicPortfolioMargins, input, &resp, false)
+}
+
+// WSChangeAPIKeyName changes the name of the api key requested through the websocket connection.
+func (d *Deribit) WSChangeAPIKeyName(id int64, name string) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ if !alphaNumericRegExp.MatchString(name) {
+ return nil, errUnacceptableAPIKey
+ }
+ input := &struct {
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ }{
+ ID: id,
+ Name: name,
+ }
+ var resp *APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, changeAPIKeyName, input, &resp, true)
+}
+
+// WsChangeMarginModel change margin model
+// Margin model: 'cross_pm', 'cross_sm', 'segregated_pm', 'segregated_sm'
+// 'dry_run': If true request returns the result without switching the margining model. Default: false
+func (d *Deribit) WsChangeMarginModel(userID int64, marginModel string, dryRun bool) ([]TogglePortfolioMarginResponse, error) {
+ if marginModel == "" {
+ return nil, errInvalidMarginModel
+ }
+ input := &struct {
+ MarginModel string `json:"margin_model"`
+ UserID int64 `json:"user_id"`
+ DryRun bool `json:"dry_run,omitempty"`
+ }{
+ MarginModel: marginModel,
+ UserID: userID,
+ DryRun: dryRun,
+ }
+ var resp []TogglePortfolioMarginResponse
+ return resp, d.SendWSRequest(nonMatchingEPL, changeMarginModel, input, &resp, true)
+}
+
+// WSChangeScopeInAPIKey changes the name of the requested subaccount id through the websocket connection.
+func (d *Deribit) WSChangeScopeInAPIKey(id int64, maxScope string) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ input := &struct {
+ ID int64 `json:"id"`
+ MaxScope string `json:"max_scope"`
+ }{
+ ID: id,
+ MaxScope: maxScope,
+ }
+ var resp *APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, changeScopeInAPIKey, input, &resp, true)
+}
+
+// WSChangeSubAccountName retrieves changes the name of the requested subaccount id through the websocket connection.
+func (d *Deribit) WSChangeSubAccountName(sid int64, name string) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ if name == "" {
+ return errInvalidUsername
+ }
+ input := &struct {
+ SID int64 `json:"sid"`
+ Name string `json:"name"`
+ }{
+ SID: sid,
+ Name: name,
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, changeSubAccountName, input, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return errSubAccountNameChangeFailed
+ }
+ return nil
+}
+
+// WSCreateAPIKey creates an api key based on the provided settings through the websocket connection.
+func (d *Deribit) WSCreateAPIKey(maxScope, name string, defaultKey bool) (*APIKeyData, error) {
+ input := &struct {
+ MaxScope string `json:"max_scope"`
+ Name string `json:"name,omitempty"`
+ Default bool `json:"default"`
+ }{
+ MaxScope: maxScope,
+ Name: name,
+ Default: defaultKey,
+ }
+
+ var resp *APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, createAPIKey, input, &resp, true)
+}
+
+// WSCreateSubAccount creates a new subaccount through the websocket connection.
+func (d *Deribit) WSCreateSubAccount() (*SubAccountData, error) {
+ var resp *SubAccountData
+ return resp, d.SendWSRequest(nonMatchingEPL, createSubAccount, nil, &resp, true)
+}
+
+// WSDisableAPIKey disables the api key linked to the provided id through the websocket connection.
+func (d *Deribit) WSDisableAPIKey(id int64) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ input := &struct {
+ ID int64 `json:"id"`
+ }{
+ ID: id,
+ }
+ var resp *APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, disableAPIKey, input, &resp, true)
+}
+
+// WsEditAPIKey edits existing API key. At least one parameter is required.
+// Describes maximal access for tokens generated with given key, possible values:
+// trade:[read, read_write, none],
+// wallet:[read, read_write, none],
+// account:[read, read_write, none],
+// block_trade:[read, read_write, none].
+func (d *Deribit) WsEditAPIKey(id int64, maxScope, name string, enabled bool, enabledFeatures, ipWhitelist []string) (*APIKeyData, error) {
+ if id == 0 {
+ return nil, errInvalidAPIKeyID
+ }
+ if maxScope == "" {
+ return nil, errMaxScopeIsRequired
+ }
+ input := &struct {
+ ID int64 `json:"id"`
+ MaxScope string `json:"max_scope"`
+ Name string `json:"name,omitempty"`
+ Enabled bool `json:"enabled,omitempty"`
+ EnabledFeatures []string `json:"enabled_features,omitempty"`
+ IPWhitelist []string `json:"ip_whitelist,omitempty"`
+ }{
+ ID: id,
+ MaxScope: maxScope,
+ Name: name,
+ Enabled: enabled,
+ EnabledFeatures: enabledFeatures,
+ IPWhitelist: ipWhitelist,
+ }
+ var resp *APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, editAPIKey, input, &resp, true)
+}
+
+// WSEnableAffiliateProgram enables the affiliate program through the websocket connection.
+func (d *Deribit) WSEnableAffiliateProgram() error {
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, enableAffiliateProgram, nil, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return errors.New("could not enable affiliate program")
+ }
+ return nil
+}
+
+// WSEnableAPIKey enables the api key linked to the provided id through the websocket connection.
+func (d *Deribit) WSEnableAPIKey(id int64) (*APIKeyData, error) {
+ if id <= 0 {
+ return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ var resp *APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, enableAPIKey, map[string]int64{"id": id}, &resp, true)
+}
+
+// WSRetrieveAccessLog lists access logs for the user through the websocket connection.
+func (d *Deribit) WSRetrieveAccessLog(offset, count int64) (*AccessLog, error) {
+ input := &struct {
+ Offset int64 `json:"offset,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ }{
+ Offset: offset,
+ Count: count,
+ }
+ var resp *AccessLog
+ return resp, d.SendWSRequest(nonMatchingEPL, getAccessLog, input, &resp, true)
+}
+
+// WSRetrieveAffiliateProgramInfo retrieves the affiliate program info through the websocket connection.
+func (d *Deribit) WSRetrieveAffiliateProgramInfo() (*AffiliateProgramInfo, error) {
+ var resp *AffiliateProgramInfo
+ return resp, d.SendWSRequest(nonMatchingEPL, getAffiliateProgramInfo, nil, &resp, true)
+}
+
+// WSRetrieveEmailLanguage retrieves the current language set for the email through the websocket connection.
+func (d *Deribit) WSRetrieveEmailLanguage() (string, error) {
+ var resp string
+ return resp, d.SendWSRequest(nonMatchingEPL, getEmailLanguage, nil, &resp, true)
+}
+
+// WSRetrieveNewAnnouncements retrieves new announcements through the websocket connection.
+func (d *Deribit) WSRetrieveNewAnnouncements() ([]Announcement, error) {
+ var resp []Announcement
+ return resp, d.SendWSRequest(nonMatchingEPL, getNewAnnouncements, nil, &resp, true)
+}
+
+// WSRetrievePrivatePortfolioMargins alculates portfolio margin info for simulated position or current position of the user through the websocket connection. This request has special restricted rate limit (not more than once per a second).
+func (d *Deribit) WSRetrievePrivatePortfolioMargins(ccy currency.Code, accPositions bool, simulatedPositions map[string]float64) (*PortfolioMargin, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ AccountPositions bool `json:"acc_positions,omitempty"`
+ SimulatedPositions map[string]float64 `json:"simulated_positions,omitempty"`
+ }{
+ Currency: ccy,
+ AccountPositions: accPositions,
+ }
+ if len(simulatedPositions) != 0 {
+ input.SimulatedPositions = simulatedPositions
+ }
+ var resp *PortfolioMargin
+ return resp, d.SendWSRequest(portfolioMarginEPL, getPrivatePortfolioMargins, input, &resp, true)
+}
+
+// WSRetrievePosition retrieves the data of all positions in the requested instrument name through the websocket connection.
+func (d *Deribit) WSRetrievePosition(instrument string) (*PositionData, error) {
+ if instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ var resp *PositionData
+ return resp, d.SendWSRequest(nonMatchingEPL, getPosition, map[string]string{"instrument_name": instrument}, &resp, true)
+}
+
+// WSRetrieveSubAccounts retrieves all subaccounts' data through the websocket connection.
+func (d *Deribit) WSRetrieveSubAccounts(withPortfolio bool) ([]SubAccountData, error) {
+ var resp []SubAccountData
+ return resp, d.SendWSRequest(nonMatchingEPL, getSubAccounts, map[string]bool{"with_portfolio": withPortfolio}, &resp, true)
+}
+
+// WSRetrieveSubAccountDetails retrieves sub-account detail information through the websocket connection.
+func (d *Deribit) WSRetrieveSubAccountDetails(ccy currency.Code, withOpenOrders bool) ([]SubAccountDetail, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ WithOpenOrders bool `json:"with_open_orders,omitempty"`
+ }{
+ Currency: ccy,
+ WithOpenOrders: withOpenOrders,
+ }
+ var resp []SubAccountDetail
+ return resp, d.SendWSRequest(nonMatchingEPL, getSubAccountDetails, input, &resp, true)
+}
+
+// WSRetrievePositions retrieves positions data of the user account through the websocket connection.
+func (d *Deribit) WSRetrievePositions(ccy currency.Code, kind string) ([]PositionData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ }
+ var resp []PositionData
+ return resp, d.SendWSRequest(nonMatchingEPL, getPositions, input, &resp, true)
+}
+
+// WSRetrieveTransactionLog retrieves transaction logs data through the websocket connection.
+func (d *Deribit) WSRetrieveTransactionLog(ccy currency.Code, query string, startTime, endTime time.Time, count, continuation int64) (*TransactionsData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Query string `json:"query,omitempty"`
+ StartTimestamp int64 `json:"start_timestamp,omitempty"`
+ EndTimestamp int64 `json:"end_timestamp,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ Continuation int64 `json:"continuation,omitempty"`
+ }{
+ Currency: ccy,
+ Query: query,
+ StartTimestamp: startTime.UnixMilli(),
+ EndTimestamp: endTime.UnixMilli(),
+ Count: count,
+ Continuation: continuation,
+ }
+ var resp *TransactionsData
+ return resp, d.SendWSRequest(nonMatchingEPL, getTransactionLog, input, &resp, true)
+}
+
+// WSRetrieveUserLocks retrieves information about locks on user account through the websocket connection.
+func (d *Deribit) WSRetrieveUserLocks() ([]UserLock, error) {
+ var resp []UserLock
+ return resp, d.SendWSRequest(nonMatchingEPL, getUserLocks, nil, &resp, true)
+}
+
+// WSListAPIKeys retrieves all the api keys associated with a user account through the websocket connection.
+func (d *Deribit) WSListAPIKeys(tfa string) ([]APIKeyData, error) {
+ var resp []APIKeyData
+ return resp, d.SendWSRequest(nonMatchingEPL, listAPIKeys, map[string]string{"tfa": tfa}, &resp, true)
+}
+
+// WsRetrieveCustodyAccounts retrieves user custody accounts
+func (d *Deribit) WsRetrieveCustodyAccounts(ccy currency.Code) ([]CustodyAccount, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ var resp []CustodyAccount
+ return resp, d.SendWSRequest(nonMatchingEPL, listCustodyAccounts, &map[string]string{"currency": ccy.String()}, &resp, true)
+}
+
+// WSRemoveAPIKey removes api key vid ID through the websocket connection.
+func (d *Deribit) WSRemoveAPIKey(id int64) error {
+ if id <= 0 {
+ return fmt.Errorf("%w, invalid api key id", errInvalidID)
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, removeAPIKey, map[string]int64{"id": id}, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return errors.New("removal of the api key requested failed")
+ }
+ return nil
+}
+
+// WSRemoveSubAccount removes a subaccount given its id through the websocket connection.
+func (d *Deribit) WSRemoveSubAccount(subAccountID int64) error {
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, removeSubAccount, map[string]int64{"subaccount_id": subAccountID}, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("removal of sub account %v failed", subAccountID)
+ }
+ return nil
+}
+
+// WSResetAPIKey sets an announcement as read through the websocket connection.
+func (d *Deribit) WSResetAPIKey(id int64) error {
+ if id <= 0 {
+ return fmt.Errorf("%w, invalid announcement id", errInvalidID)
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, resetAPIKey, map[string]int64{"announcement_id": id}, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("setting announcement %v as read failed", id)
+ }
+ return nil
+}
+
+// WSSetEmailForSubAccount links an email given to the designated subaccount through the websocket connection.
+func (d *Deribit) WSSetEmailForSubAccount(sid int64, email string) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ if !common.MatchesEmailPattern(email) {
+ return errInvalidEmailAddress
+ }
+ input := &struct {
+ SID int64 `json:"sid"`
+ Email string `json:"email"`
+ }{
+ Email: email,
+ SID: sid,
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, setEmailForSubAccount, input, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("could not link email (%v) to subaccount %v", email, sid)
+ }
+ return nil
+}
+
+// WSSetEmailLanguage sets a requested language for an email through the websocket connection.
+func (d *Deribit) WSSetEmailLanguage(language string) error {
+ if language == "" {
+ return errLanguageIsRequired
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, setEmailLanguage, map[string]string{"language": language}, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("could not set the email language to %v", language)
+ }
+ return nil
+}
+
+// WsSetSelfTradingConfig configure self trading behavior through the websocket connection.
+// mode: Self trading prevention behavior. Possible values: 'reject_taker', 'cancel_maker'
+// extended_to_subaccounts: If value is true trading is prevented between subaccounts of given account
+func (d *Deribit) WsSetSelfTradingConfig(mode string, extendedToSubaccounts bool) (string, error) {
+ if mode == "" {
+ return "", errTradeModeIsRequired
+ }
+ input := &struct {
+ Mode string `json:"mode"`
+ ExtendedToSubAccounts bool `json:"extended_to_subaccounts"`
+ }{
+ Mode: mode,
+ ExtendedToSubAccounts: extendedToSubaccounts,
+ }
+ var resp string
+ return resp, d.SendWSRequest(nonMatchingEPL, setSelfTradingConfig, input, &resp, true)
+}
+
+// WSToggleNotificationsFromSubAccount toggles the notifications from a subaccount specified through the websocket connection.
+func (d *Deribit) WSToggleNotificationsFromSubAccount(sid int64, state bool) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ input := &struct {
+ SID int64 `json:"sid"`
+ State bool `json:"state"`
+ }{
+ SID: sid,
+ State: state,
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, toggleNotificationsFromSubAccount, input, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("toggling notifications for subaccount %v to %v failed", sid, state)
+ }
+ return nil
+}
+
+// WSTogglePortfolioMargining toggle between SM and PM models through the websocket connection.
+func (d *Deribit) WSTogglePortfolioMargining(userID int64, enabled, dryRun bool) ([]TogglePortfolioMarginResponse, error) {
+ if userID == 0 {
+ return nil, errUserIDRequired
+ }
+ input := &struct {
+ UserID int64 `json:"user_id"`
+ Enabled bool `json:"enabled"`
+ DryRun bool `json:"dry_run"`
+ }{
+ UserID: userID,
+ Enabled: enabled,
+ DryRun: dryRun,
+ }
+ var resp []TogglePortfolioMarginResponse
+ return resp, d.SendWSRequest(nonMatchingEPL, togglePortfolioMargining, input, &resp, true)
+}
+
+// WSToggleSubAccountLogin toggles access for subaccount login through the websocket connection.
+func (d *Deribit) WSToggleSubAccountLogin(sid int64, state bool) error {
+ if sid <= 0 {
+ return fmt.Errorf("%w, invalid subaccount user id", errInvalidID)
+ }
+ input := &struct {
+ SID int64 `json:"sid"`
+ State bool `json:"state"`
+ }{
+ SID: sid,
+ State: state,
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, toggleSubAccountLogin, input, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("toggling login access for subaccount %v to %v failed", sid, state)
+ }
+ return nil
+}
+
+// WSSubmitBuy submits a private buy request through the websocket connection.
+func (d *Deribit) WSSubmitBuy(arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w parameter is required", common.ErrNilPointer)
+ }
+ if arg.Instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendWSRequest(matchingEPL, submitBuy, &arg, &resp, true)
+}
+
+// WSSubmitSell submits a sell request with the parameters provided through the websocket connection.
+func (d *Deribit) WSSubmitSell(arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w parameter is required", common.ErrNilPointer)
+ }
+ if arg.Instrument == "" {
+ return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendWSRequest(matchingEPL, submitSell, &arg, &resp, true)
+}
+
+// WSSubmitEdit submits an edit order request through the websocket connection.
+func (d *Deribit) WSSubmitEdit(arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, common.ErrNilPointer
+ }
+ if arg.OrderID == "" {
+ return nil, fmt.Errorf("%w, order id is required", errInvalidID)
+ }
+ if arg.Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendWSRequest(matchingEPL, submitEdit, &arg, &resp, true)
+}
+
+// WSEditOrderByLabel submits an edit order request sorted via label through the websocket connection.
+func (d *Deribit) WSEditOrderByLabel(arg *OrderBuyAndSellParams) (*PrivateTradeData, error) {
+ if arg == nil || *arg == (OrderBuyAndSellParams{}) {
+ return nil, fmt.Errorf("%w argument cannot be null", common.ErrNilPointer)
+ }
+ if arg.Instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ if arg.Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendWSRequest(nonMatchingEPL, editByLabel, &arg, &resp, true)
+}
+
+// WSSubmitCancel sends a request to cancel the order via its orderID through the websocket connection.
+func (d *Deribit) WSSubmitCancel(orderID string) (*PrivateCancelData, error) {
+ if orderID == "" {
+ return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
+ }
+ var resp *PrivateCancelData
+ return resp, d.SendWSRequest(matchingEPL, submitCancel, map[string]string{"order_id": orderID}, &resp, true)
+}
+
+// WSSubmitCancelAll sends a request to cancel all user orders in all currencies and instruments
+func (d *Deribit) WSSubmitCancelAll(detailed bool) (*MultipleCancelResponse, error) {
+ var resp *MultipleCancelResponse
+ return resp, d.SendWSRequest(matchingEPL, submitCancelAll, map[string]bool{"detailed": detailed}, &resp, true)
+}
+
+// WSSubmitCancelAllByCurrency sends a request to cancel all user orders for the specified currency through the websocket connection.
+func (d *Deribit) WSSubmitCancelAllByCurrency(ccy currency.Code, kind, orderType string, detailed bool) (*MultipleCancelResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ OrderType string `json:"order_type,omitempty"`
+ Detailed bool `json:"detailed"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ OrderType: orderType,
+ Detailed: detailed,
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendWSRequest(matchingEPL, submitCancelAllByCurrency, input, &resp, true)
+}
+
+// WSSubmitCancelAllByInstrument sends a request to cancel all user orders for the specified instrument through the websocket connection.
+func (d *Deribit) WSSubmitCancelAllByInstrument(instrument, orderType string, detailed, includeCombos bool) (*MultipleCancelResponse, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ OrderType string `json:"type,omitempty"`
+ Detailed bool `json:"detailed,omitempty"`
+ IncludeCombos bool `json:"include_combos,omitempty"`
+ }{
+ Instrument: instrument,
+ OrderType: orderType,
+ Detailed: detailed,
+ IncludeCombos: includeCombos,
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendWSRequest(matchingEPL, submitCancelAllByInstrument, input, &resp, true)
+}
+
+// WsSubmitCancelAllByKind cancels all orders in currency(currencies), optionally filtered by instrument kind and/or order type.
+// 'kind' Instrument kind. Possible values: 'future', 'option', 'spot', 'future_combo', 'option_combo', 'combo', 'any'
+func (d *Deribit) WsSubmitCancelAllByKind(ccy currency.Code, kind, orderType string, detailed bool) (*MultipleCancelResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency string `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ OrderType string `json:"type,omitempty"`
+ Detailed bool `json:"detailed,omitempty"`
+ }{
+ Currency: ccy.String(),
+ Kind: kind,
+ OrderType: orderType,
+ Detailed: detailed,
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendWSRequest(matchingEPL, submitCancelAllByKind, input, &resp, true)
+}
+
+// WSSubmitCancelByLabel sends a request to cancel all user orders for the specified label through the websocket connection.
+func (d *Deribit) WSSubmitCancelByLabel(label string, ccy currency.Code, detailed bool) (*MultipleCancelResponse, error) {
+ input := &struct {
+ Label string `json:"label"`
+ Currency string `json:"currency,omitempty"`
+ Detailed bool `json:"detailed,omitempty"`
+ }{
+ Label: label,
+ Currency: ccy.String(),
+ Detailed: detailed,
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendWSRequest(matchingEPL, submitCancelByLabel, input, &resp, true)
+}
+
+// WSSubmitCancelQuotes cancels quotes based on the provided type.
+//
+// possible cancel_type values are delta, 'quote_set_id', 'instrument', 'instrument_kind', 'currency', and 'all'
+// possible kind values are future 'option', 'spot', 'future_combo', 'option_combo', 'combo', and 'any'
+func (d *Deribit) WSSubmitCancelQuotes(ccy currency.Code, minDelta, maxDelta float64, cancelType, quoteSetID, instrumentName, kind string, detailed bool) (*MultipleCancelResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if cancelType == "" {
+ return nil, errors.New("cancel type is required")
+ }
+ input := &struct {
+ CancelType string `json:"cancel_type"`
+ Currency string `json:"currency"`
+ Detailed bool `json:"detailed,omitempty"`
+ MinDelta float64 `json:"min_delta,omitempty"`
+ MaxDelta float64 `json:"max_delta,omitempty"`
+ InstrumentName string `json:"instrument_name,omitempty"`
+ QuoteSetID string `json:"quote_set_id,omitempty"`
+ Kind string `json:"kind,omitempty"`
+ }{
+ CancelType: cancelType,
+ Currency: ccy.String(),
+ Detailed: detailed,
+ MinDelta: minDelta,
+ MaxDelta: maxDelta,
+ InstrumentName: instrumentName,
+ Kind: kind,
+ QuoteSetID: quoteSetID,
+ }
+ var resp *MultipleCancelResponse
+ return resp, d.SendWSRequest(matchingEPL, submitCancelQuotes, input, &resp, true)
+}
+
+// WSSubmitClosePosition sends a request to cancel all user orders for the specified label through the websocket connection.
+func (d *Deribit) WSSubmitClosePosition(instrument, orderType string, price float64) (*PrivateTradeData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Type string `json:"type,omitempty"`
+ Price float64 `json:"price"`
+ }{
+ Instrument: instrument,
+ Type: orderType,
+ Price: price,
+ }
+ var resp *PrivateTradeData
+ return resp, d.SendWSRequest(matchingEPL, submitClosePosition, input, &resp, true)
+}
+
+// WSRetrieveMargins sends a request to fetch account margins data through the websocket connection.
+func (d *Deribit) WSRetrieveMargins(instrument string, amount, price float64) (*MarginsData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ if amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if price <= 0 {
+ return nil, errInvalidPrice
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Amount float64 `json:"amount"`
+ Price float64 `json:"price"`
+ }{
+ Instrument: instrument,
+ Amount: amount,
+ Price: price,
+ }
+ var resp *MarginsData
+ return resp, d.SendWSRequest(nonMatchingEPL, getMargins, input, &resp, true)
+}
+
+// WSRetrieveMMPConfig sends a request to fetch the config for MMP of the requested currency through the websocket connection.
+func (d *Deribit) WSRetrieveMMPConfig(ccy currency.Code) (*MMPConfigData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ var resp *MMPConfigData
+ return resp, d.SendWSRequest(nonMatchingEPL, getMMPConfig, map[string]currency.Code{"currency": ccy}, &resp, true)
+}
+
+// WSRetrieveOpenOrdersByCurrency retrieves open order by symbol and kind
+func (d *Deribit) WSRetrieveOpenOrdersByCurrency(ccy currency.Code, kind, orderType string) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ OrderType string `json:"type,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ OrderType: orderType,
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOpenOrdersByCurrency, input, &resp, true)
+}
+
+// WSRetrieveOpenOrdersByLabel retrieves open order by label and currency
+func (d *Deribit) WSRetrieveOpenOrdersByLabel(ccy currency.Code, label string) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Label string `json:"label"`
+ }{
+ Currency: ccy,
+ Label: label,
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOpenOrdersByLabel, input, &resp, true)
+}
+
+// WSRetrieveOpenOrdersByInstrument sends a request to fetch open orders data sorted by requested params through the websocket connection.
+func (d *Deribit) WSRetrieveOpenOrdersByInstrument(instrument, orderType string) ([]OrderData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Type string `json:"type,omitempty"`
+ }{
+ Instrument: instrument,
+ Type: orderType,
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOpenOrdersByInstrument, input, &resp, true)
+}
+
+// WSRetrieveOrderHistoryByCurrency sends a request to fetch order history according to given params and currency through the websocket connection.
+func (d *Deribit) WSRetrieveOrderHistoryByCurrency(ccy currency.Code, kind string, count, offset int64, includeOld, includeUnfilled bool) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ Offset int64 `json:"offset,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ IncludeUnfilled bool `json:"include_unfilled,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ Count: count,
+ Offset: offset,
+ IncludeOld: includeOld,
+ IncludeUnfilled: includeUnfilled,
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderHistoryByCurrency, input, &resp, true)
+}
+
+// WSRetrieveOrderHistoryByInstrument sends a request to fetch order history according to given params and instrument through the websocket connection.
+func (d *Deribit) WSRetrieveOrderHistoryByInstrument(instrument string, count, offset int64, includeOld, includeUnfilled bool) ([]OrderData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Count int64 `json:"count,omitempty"`
+ Offset int64 `json:"offset,omitempty"`
+ IncludeOld bool `json:"include_old"`
+ IncludeUnfilled bool `json:"include_unfilled"`
+ }{
+ Instrument: instrument,
+ Count: count,
+ Offset: offset,
+ IncludeOld: includeOld,
+ IncludeUnfilled: includeUnfilled,
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderHistoryByInstrument, input, &resp, true)
+}
+
+// WSRetrieveOrderMarginsByID sends a request to fetch order margins data according to their ids through the websocket connection.
+func (d *Deribit) WSRetrieveOrderMarginsByID(ids []string) ([]OrderData, error) {
+ if len(ids) == 0 {
+ return nil, fmt.Errorf("%w, order ids cannot be empty", errInvalidID)
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderMarginByIDs, map[string][]string{"ids": ids}, &resp, true)
+}
+
+// WSRetrievesOrderState sends a request to fetch order state of the order id provided
+func (d *Deribit) WSRetrievesOrderState(orderID string) (*OrderData, error) {
+ if orderID == "" {
+ return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
+ }
+ var resp *OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderState, map[string]string{"order_id": orderID}, &resp, true)
+}
+
+// WsRetrieveOrderStateByLabel retrieves an order state by label and currency
+func (d *Deribit) WsRetrieveOrderStateByLabel(ccy currency.Code, label string) ([]OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Label string `json:"label"`
+ }{
+ Currency: ccy,
+ Label: label,
+ }
+ var resp []OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getOrderStateByLabel, input, &resp, true)
+}
+
+// WSRetrieveTriggerOrderHistory sends a request to fetch order state of the order id provided through the websocket connection.
+func (d *Deribit) WSRetrieveTriggerOrderHistory(ccy currency.Code, instrumentName, continuation string, count int64) (*OrderData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency string `json:"currency,omitempty"`
+ Instrument string `json:"instrument,omitempty"`
+ Continuation string `json:"continuation,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ }{
+ Currency: ccy.String(),
+ Instrument: instrumentName,
+ Continuation: continuation,
+ Count: count,
+ }
+ var resp *OrderData
+ return resp, d.SendWSRequest(nonMatchingEPL, getTriggerOrderHistory, input, &resp, true)
+}
+
+// WSRetrieveUserTradesByCurrency sends a request to fetch user trades sorted by currency through the websocket connection.
+func (d *Deribit) WSRetrieveUserTradesByCurrency(ccy currency.Code, kind, startID, endID, sorting string, count int64, includeOld bool) (*UserTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind"`
+ StartID string `json:"start_id,omitempty"`
+ EndID string `json:"end_id,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ StartID: startID,
+ EndID: endID,
+ Sorting: sorting,
+ Count: count,
+ IncludeOld: includeOld,
+ }
+ var resp *UserTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getUserTradesByCurrency, input, &resp, true)
+}
+
+// WSRetrieveUserTradesByCurrencyAndTime retrieves user trades sorted by currency and time through the websocket connection.
+func (d *Deribit) WSRetrieveUserTradesByCurrencyAndTime(ccy currency.Code, kind, sorting string, count int64, startTime, endTime time.Time) (*UserTradesData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Kind string `json:"kind,omitempty"`
+ StartTime int64 `json:"start_timestamp,omitempty"`
+ EndTime int64 `json:"end_timestamp,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ }{
+ Currency: ccy,
+ Kind: kind,
+ Sorting: sorting,
+ Count: count,
+ }
+ if !startTime.IsZero() {
+ input.StartTime = startTime.UnixMilli()
+ }
+ if !endTime.IsZero() {
+ input.EndTime = endTime.UnixMilli()
+ }
+ var resp *UserTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getUserTradesByCurrencyAndTime, input, &resp, true)
+}
+
+// WsRetrieveUserTradesByInstrument retrieves user trades sorted by instrument through the websocket connection.
+func (d *Deribit) WsRetrieveUserTradesByInstrument(instrument, sorting string, startSeq, endSeq, count int64, includeOld bool) (*UserTradesData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ StartSeq int64 `json:"start_seq,omitempty"`
+ EndSeq int64 `json:"end_seq,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ }{
+ Instrument: instrument,
+ StartSeq: startSeq,
+ EndSeq: endSeq,
+ Sorting: sorting,
+ Count: count,
+ IncludeOld: includeOld,
+ }
+ var resp *UserTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getUserTradesByInstrument, input, &resp, true)
+}
+
+// WSRetrieveUserTradesByInstrumentAndTime retrieves user trades sorted by instrument and time through the websocket connection.
+func (d *Deribit) WSRetrieveUserTradesByInstrumentAndTime(instrument, sorting string, count int64, includeOld bool, startTime, endTime time.Time) (*UserTradesData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ if err := common.StartEndTimeCheck(startTime, endTime); err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ StartTime int64 `json:"start_timestamp,omitempty"`
+ EndTime int64 `json:"end_timestamp,omitempty"`
+ Sorting string `json:"sorting,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ IncludeOld bool `json:"include_old,omitempty"`
+ }{
+ Instrument: instrument,
+ StartTime: startTime.UnixMilli(),
+ EndTime: endTime.UnixMilli(),
+ Sorting: sorting,
+ Count: count,
+ IncludeOld: includeOld,
+ }
+ var resp *UserTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getUserTradesByInstrumentAndTime, input, &resp, true)
+}
+
+// WSRetrieveUserTradesByOrder retrieves user trades fetched by orderID through the web socket connection.
+func (d *Deribit) WSRetrieveUserTradesByOrder(orderID, sorting string) (*UserTradesData, error) {
+ if orderID == "" {
+ return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
+ }
+ input := &struct {
+ OrderID string `json:"order_id"`
+ Sorting string `json:"sorting,omitempty"`
+ }{
+ OrderID: orderID,
+ Sorting: sorting,
+ }
+ var resp *UserTradesData
+ return resp, d.SendWSRequest(nonMatchingEPL, getUserTradesByOrder, input, &resp, true)
+}
+
+// WSResetMMP sends a request to reset MMP for a currency provided through the websocket connection.
+func (d *Deribit) WSResetMMP(ccy currency.Code) error {
+ if ccy.IsEmpty() {
+ return currency.ErrCurrencyCodeEmpty
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, resetMMP, map[string]currency.Code{"currency": ccy}, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("mmp could not be reset for %v", ccy.String())
+ }
+ return nil
+}
+
+// WSSendRequestForQuote sends RFQ on a given instrument through the websocket connection.
+func (d *Deribit) WSSendRequestForQuote(instrumentName string, amount float64, side order.Side) error {
+ if instrumentName == "" {
+ return errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Amount float64 `json:"amount,omitempty"`
+ Side string `json:"side,omitempty"`
+ }{
+ Instrument: instrumentName,
+ Amount: amount,
+ Side: side.String(),
+ }
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, sendRFQ, input, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("rfq couldn't send for %v", instrumentName)
+ }
+ return nil
+}
+
+// WSSetMMPConfig sends a request to set the given parameter values to the mmp config for the provided currency through the websocket connection.
+func (d *Deribit) WSSetMMPConfig(ccy currency.Code, interval kline.Interval, frozenTime int64, quantityLimit, deltaLimit float64) error {
+ if ccy.IsEmpty() {
+ return currency.ErrCurrencyCodeEmpty
+ }
+ params := make(map[string]interface{})
+ params["currency"] = ccy
+ intervalString, err := d.GetResolutionFromInterval(interval)
+ if err != nil {
+ return err
+ }
+ params["interval"] = intervalString
+ params["frozen_time"] = frozenTime
+ if quantityLimit != 0 {
+ params["quantity_time"] = quantityLimit
+ }
+ if deltaLimit != 0 {
+ params["delta_limit"] = deltaLimit
+ }
+ var resp string
+ err = d.SendWSRequest(nonMatchingEPL, setMMPConfig, params, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("mmp data could not be set for %v", ccy.String())
+ }
+ return nil
+}
+
+// WSRetrieveSettlementHistoryByInstrument sends a request to fetch settlement history data sorted by instrument through the websocket connection.
+func (d *Deribit) WSRetrieveSettlementHistoryByInstrument(instrument, settlementType, continuation string, count int64, searchStartTimeStamp time.Time) (*PrivateSettlementsHistoryData, error) {
+ if instrument == "" {
+ return nil, errInvalidInstrumentName
+ }
+ input := &struct {
+ Instrument string `json:"instrument_name"`
+ Continuation string `json:"continuation,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ SearchStartTimestamp int64 `json:"search_start_timestamp,omitempty"`
+ Type string `json:"type,omitempty"`
+ }{
+ Instrument: instrument,
+ Continuation: continuation,
+ Count: count,
+ Type: settlementType,
+ }
+ if !searchStartTimeStamp.IsZero() {
+ input.SearchStartTimestamp = searchStartTimeStamp.UnixMilli()
+ }
+ var resp *PrivateSettlementsHistoryData
+ return resp, d.SendWSRequest(nonMatchingEPL, getSettlementHistoryByInstrument, input, &resp, true)
+}
+
+// WSRetrieveSettlementHistoryByCurency sends a request to fetch settlement history data sorted by currency through the websocket connection.
+func (d *Deribit) WSRetrieveSettlementHistoryByCurency(ccy currency.Code, settlementType, continuation string, count int64, searchStartTimeStamp time.Time) (*PrivateSettlementsHistoryData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ SettlementType string `json:"settlement_type,omitempty"`
+ Continuation string `json:"continuation,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ SearchStartTimestamp int64 `json:"search_start_timestamp,omitempty"`
+ }{
+ Currency: ccy,
+ SettlementType: settlementType,
+ Continuation: continuation,
+ Count: count,
+ }
+ if !searchStartTimeStamp.IsZero() {
+ input.SearchStartTimestamp = searchStartTimeStamp.UnixMilli()
+ }
+ var resp *PrivateSettlementsHistoryData
+ return resp, d.SendWSRequest(nonMatchingEPL, getSettlementHistoryByCurrency, input, &resp, true)
+}
+
+// WSRetrieveComboIDs Retrieves available combos.
+// This method can be used to get the list of all combos, or only the list of combos in the given state.
+func (d *Deribit) WSRetrieveComboIDs(ccy currency.Code, state string) ([]string, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ State string `json:"state,omitempty"`
+ }{
+ Currency: ccy,
+ State: state,
+ }
+ var resp []string
+ return resp, d.SendWSRequest(nonMatchingEPL, getComboIDs, input, &resp, false)
+}
+
+// WSRetrieveComboDetails retrieves information about a combo through the websocket connection.
+func (d *Deribit) WSRetrieveComboDetails(comboID string) (*ComboDetail, error) {
+ if comboID == "" {
+ return nil, errInvalidComboID
+ }
+ var resp *ComboDetail
+ return resp, d.SendWSRequest(nonMatchingEPL, getComboDetails, map[string]string{"combo_id": comboID}, &resp, false)
+}
+
+// WSRetrieveCombos retrieves information about active combos through the websocket connection.
+func (d *Deribit) WSRetrieveCombos(ccy currency.Code) ([]ComboDetail, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ var resp []ComboDetail
+ return resp, d.SendWSRequest(nonMatchingEPL, getCombos, map[string]currency.Code{"currency": ccy}, &resp, false)
+}
+
+// WSCreateCombo verifies and creates a combo book or returns an existing combo matching given trades through the websocket connection.
+func (d *Deribit) WSCreateCombo(args []ComboParam) (*ComboDetail, error) {
+ if len(args) == 0 {
+ return nil, errNoArgumentPassed
+ }
+ for x := range args {
+ if args[x].InstrumentName == "" {
+ return nil, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ args[x].Direction = strings.ToLower(args[x].Direction)
+ if args[x].Direction != sideBUY && args[x].Direction != sideSELL {
+ return nil, errInvalidOrderSideOrDirection
+ }
+ if args[x].Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ }
+ var resp *ComboDetail
+ return resp, d.SendWSRequest(nonMatchingEPL, createCombos, map[string]interface{}{"trades": args}, &resp, true)
+}
+
+// WsLogout gracefully close websocket connection, when COD (Cancel On Disconnect) is enabled orders are not cancelled
+func (d *Deribit) WsLogout(invalidateToken bool) error {
+ input := struct {
+ InvalidateToken bool `json:"invalidate_token,omitempty"`
+ }{
+ InvalidateToken: invalidateToken,
+ }
+ return d.SendWSRequest(nonMatchingEPL, "private/logout", input, &struct{}{}, true)
+}
+
+// WsEnableCancelOnDisconnect enable Cancel On Disconnect for the connection.
+// After enabling Cancel On Disconnect all orders created by the connection will be removed when the connection is closed.
+func (d *Deribit) WsEnableCancelOnDisconnect(scope string) (string, error) {
+ input := &struct {
+ Scope string `json:"scope,omitempty"`
+ }{
+ Scope: scope,
+ }
+ var resp string
+ return resp, d.SendWSRequest(nonMatchingEPL, "private/enable_cancel_on_disconnect", input, &resp, true)
+}
+
+// WsDisableCancelOnDisconnect isable Cancel On Disconnect for the connection.
+// When change is applied for the account, then every newly opened connection will start with inactive Cancel on Disconnect.
+// scope: possible values are 'connection', 'account'
+func (d *Deribit) WsDisableCancelOnDisconnect(scope string) (string, error) {
+ input := &struct {
+ Scope string `json:"scope,omitempty"`
+ }{
+ Scope: scope,
+ }
+ var resp string
+ return resp, d.SendWSRequest(nonMatchingEPL, "private/disable_cancel_on_disconnect", input, &resp, true)
+}
+
+// SayHello method used to introduce the client software connected to Deribit platform over websocket.
+// It returns version information
+func (d *Deribit) SayHello(clientName, clientVersion string) (*Info, error) {
+ if clientName == "" {
+ return nil, errors.New("client name is required")
+ }
+ input := &struct {
+ ClientName string `json:"client_name"`
+ ClientVersion string `json:"client_version"`
+ }{
+ ClientName: clientName,
+ ClientVersion: clientVersion,
+ }
+ var resp *Info
+ return resp, d.SendWSRequest(nonMatchingEPL, "public/hello", input, &resp, false)
+}
+
+// WsRetrieveCancelOnDisconnect read current Cancel On Disconnect configuration for the account.
+// 'scope': Specifies if Cancel On Disconnect change should be applied/checked for the current connection or the account (default - connection)
+// Scope connection can be used only when working via Websocket.
+func (d *Deribit) WsRetrieveCancelOnDisconnect(scope string) (*CancelOnDisconnect, error) {
+ input := &struct {
+ Scope string `json:"scope,omitempty"`
+ }{
+ Scope: scope,
+ }
+ var resp *CancelOnDisconnect
+ return resp, d.SendWSRequest(nonMatchingEPL, "private/get_cancel_on_disconnect", input, &resp, true)
+}
+
+// WsExchangeToken generates a token for a new subject id. This method can be used to switch between subaccounts.
+func (d *Deribit) WsExchangeToken(refreshToken string, subjectID int64) (*RefreshTokenInfo, error) {
+ if refreshToken == "" {
+ return nil, errRefreshTokenRequired
+ }
+ if subjectID == 0 {
+ return nil, errors.New("subject id is required")
+ }
+ input := &struct {
+ RefreshToken string `json:"retresh_token"`
+ SubjectID int64 `json:"subject_id"`
+ }{
+ RefreshToken: refreshToken,
+ SubjectID: subjectID,
+ }
+ var resp *RefreshTokenInfo
+ return resp, d.SendWSRequest(nonMatchingEPL, "public/exchange_token", input, &resp, true)
+}
+
+// WsForkToken generates a token for a new named session. This method can be used only with session scoped tokens.
+func (d *Deribit) WsForkToken(refreshToken, sessionName string) (*RefreshTokenInfo, error) {
+ if refreshToken == "" {
+ return nil, errRefreshTokenRequired
+ }
+ if sessionName == "" {
+ return nil, errSessionNameRequired
+ }
+ input := &struct {
+ RefreshToken string `json:"refresh_token"`
+ SessionName string `json:"session_name"`
+ }{
+ RefreshToken: refreshToken,
+ SessionName: sessionName,
+ }
+ var resp *RefreshTokenInfo
+ return resp, d.SendWSRequest(nonMatchingEPL, "public/fork_token", input, &resp, true)
+}
+
+// UnsubscribeAll unsubscribe from all the public channels subscribed so far.
+func (d *Deribit) UnsubscribeAll() (string, error) {
+ var resp string
+ return resp, d.SendWSRequest(nonMatchingEPL, "public/unsubscribe_all", nil, &resp, false)
+}
+
+// UnsubscribeAllPrivateChannels sends an unsubscribe request to cancel all private channels subscriptions
+func (d *Deribit) UnsubscribeAllPrivateChannels() (string, error) {
+ var resp string
+ return resp, d.SendWSRequest(nonMatchingEPL, "private/unsubscribe_all", nil, &resp, false)
+}
+
+// ------------------------------------------------------------------------------------------------
+
+// WSExecuteBlockTrade executes a block trade request
+// The whole request have to be exact the same as in private/verify_block_trade, only role field should be set appropriately - it basically means that both sides have to agree on the same timestamp, nonce, trades fields and server will assure that role field is different between sides (each party accepted own role).
+// Using the same timestamp and nonce by both sides in private/verify_block_trade assures that even if unintentionally both sides execute given block trade with valid counterparty_signature, the given block trade will be executed only once
+func (d *Deribit) WSExecuteBlockTrade(timestampMS time.Time, nonce, role string, ccy currency.Code, trades []BlockTradeParam) ([]BlockTradeResponse, error) {
+ if nonce == "" {
+ return nil, errMissingNonce
+ }
+ if role != roleMaker && role != roleTaker {
+ return nil, errInvalidTradeRole
+ }
+ if len(trades) == 0 {
+ return nil, errNoArgumentPassed
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return nil, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ trades[x].Direction = strings.ToLower(trades[x].Direction)
+ if trades[x].Direction != sideBUY && trades[x].Direction != sideSELL {
+ return nil, errInvalidOrderSideOrDirection
+ }
+ if trades[x].Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return nil, fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ signature, err := d.WSVerifyBlockTrade(timestampMS, nonce, role, ccy, trades)
+ if err != nil {
+ return nil, err
+ }
+ input := &struct {
+ Nonce string `json:"nonce"`
+ Role string `json:"role,omitempty"`
+ CounterpartySignature string `json:"counterparty_signature"`
+ Trades []BlockTradeParam `json:"trades"`
+ Timestamp int64 `json:"timestamp"`
+ Currency string `json:"currency,omitempty"`
+ }{
+ Nonce: nonce,
+ Role: role,
+ CounterpartySignature: signature,
+ Trades: trades,
+ Timestamp: timestampMS.UnixMilli(),
+ Currency: ccy.String(),
+ }
+ var resp []BlockTradeResponse
+ return resp, d.SendWSRequest(matchingEPL, executeBlockTrades, input, &resp, true)
+}
+
+// WSVerifyBlockTrade verifies and creates block trade signature through the websocket connection.
+func (d *Deribit) WSVerifyBlockTrade(timestampMS time.Time, nonce, role string, ccy currency.Code, trades []BlockTradeParam) (string, error) {
+ if nonce == "" {
+ return "", errMissingNonce
+ }
+ if role != roleMaker && role != roleTaker {
+ return "", errInvalidTradeRole
+ }
+ if len(trades) == 0 {
+ return "", errNoArgumentPassed
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return "", fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ trades[x].Direction = strings.ToLower(trades[x].Direction)
+ if trades[x].Direction != sideBUY && trades[x].Direction != sideSELL {
+ return "", errInvalidOrderSideOrDirection
+ }
+ if trades[x].Amount <= 0 {
+ return "", errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return "", fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ if timestampMS.IsZero() {
+ return "", errZeroTimestamp
+ }
+ input := &struct {
+ Nonce string `json:"nonce"`
+ Role string `json:"role,omitempty"`
+ CounterpartySignature string `json:"counterparty_signature"`
+ Trades []BlockTradeParam `json:"trades"`
+ Timestamp int64 `json:"timestamp"`
+ Currency string `json:"currency,omitempty"`
+ }{
+ Nonce: nonce,
+ Role: role,
+ Trades: trades,
+ Timestamp: timestampMS.UnixMilli(),
+ Currency: ccy.String(),
+ }
+ resp := &struct {
+ Signature string `json:"signature"`
+ }{}
+ return resp.Signature, d.SendWSRequest(matchingEPL, verifyBlockTrades, input, &resp, true)
+}
+
+// WsInvalidateBlockTradeSignature user at any time (before the private/execute_block_trade is called) can invalidate its own signature effectively cancelling block trade through the websocket connection.
+func (d *Deribit) WsInvalidateBlockTradeSignature(signature string) error {
+ if signature == "" {
+ return errMissingSignature
+ }
+ params := url.Values{}
+ params.Set("signature", signature)
+ var resp string
+ err := d.SendWSRequest(nonMatchingEPL, invalidateBlockTradesSignature, params, &resp, true)
+ if err != nil {
+ return err
+ }
+ if resp != "ok" {
+ return fmt.Errorf("server response: %s", resp)
+ }
+ return nil
+}
+
+// WSRetrieveUserBlockTrade returns information about users block trade through the websocket connection.
+func (d *Deribit) WSRetrieveUserBlockTrade(id string) ([]BlockTradeData, error) {
+ if id == "" {
+ return nil, errMissingBlockTradeID
+ }
+ var resp []BlockTradeData
+ return resp, d.SendWSRequest(nonMatchingEPL, getBlockTrades, map[string]string{"id": id}, &resp, true)
+}
+
+// WSRetrieveLastBlockTradesByCurrency returns list of last users block trades through the websocket connection.
+func (d *Deribit) WSRetrieveLastBlockTradesByCurrency(ccy currency.Code, startID, endID string, count int64) ([]BlockTradeData, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ StartID string `json:"start_id,omitempty"`
+ EndID string `json:"end_id,omitempty"`
+ Count int64 `json:"count,omitempty"`
+ }{
+ Currency: ccy,
+ StartID: startID,
+ EndID: endID,
+ Count: count,
+ }
+ var resp []BlockTradeData
+ return resp, d.SendWSRequest(nonMatchingEPL, getLastBlockTradesByCurrency, input, &resp, true)
+}
+
+// WSMovePositions moves positions from source subaccount to target subaccount through the websocket connection.
+func (d *Deribit) WSMovePositions(ccy currency.Code, sourceSubAccountUID, targetSubAccountUID int64, trades []BlockTradeParam) ([]BlockTradeMoveResponse, error) {
+ if ccy.IsEmpty() {
+ return nil, currency.ErrCurrencyCodeEmpty
+ }
+ if sourceSubAccountUID == 0 {
+ return nil, fmt.Errorf("%w source sub-account id", errMissingSubAccountID)
+ }
+ if targetSubAccountUID == 0 {
+ return nil, fmt.Errorf("%w target sub-account id", errMissingSubAccountID)
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return nil, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ if trades[x].Amount <= 0 {
+ return nil, errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return nil, fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ input := &struct {
+ Currency currency.Code `json:"currency"`
+ Trades []BlockTradeParam `json:"trades"`
+ TargetUID int64 `json:"target_uid"`
+ SourceUID int64 `json:"source_uid"`
+ }{
+ Currency: ccy,
+ Trades: trades,
+ TargetUID: targetSubAccountUID,
+ SourceUID: sourceSubAccountUID,
+ }
+ var resp []BlockTradeMoveResponse
+ return resp, d.SendWSRequest(nonMatchingEPL, movePositions, input, &resp, true)
+}
+
+// WsSimulateBlockTrade checks if a block trade can be executed through the websocket stream.
+func (d *Deribit) WsSimulateBlockTrade(role string, trades []BlockTradeParam) (bool, error) {
+ if role != roleMaker && role != roleTaker {
+ return false, errInvalidTradeRole
+ }
+ if len(trades) == 0 {
+ return false, errNoArgumentPassed
+ }
+ for x := range trades {
+ if trades[x].InstrumentName == "" {
+ return false, fmt.Errorf("%w, empty string", errInvalidInstrumentName)
+ }
+ trades[x].Direction = strings.ToLower(trades[x].Direction)
+ if trades[x].Direction != sideBUY && trades[x].Direction != sideSELL {
+ return false, errInvalidOrderSideOrDirection
+ }
+ if trades[x].Amount <= 0 {
+ return false, errInvalidAmount
+ }
+ if trades[x].Price < 0 {
+ return false, fmt.Errorf("%w, trade price can't be negative", errInvalidPrice)
+ }
+ }
+ input := &struct {
+ Role string `json:"role"`
+ Trades []BlockTradeParam `json:"trades"`
+ }{
+ Role: role,
+ Trades: trades,
+ }
+ var resp bool
+ return resp, d.SendWSRequest(matchingEPL, simulateBlockPosition, input, resp, true)
+}
+
+// SendWSRequest sends a request through the websocket connection.
+// both authenticated and public endpoints are allowed.
+func (d *Deribit) SendWSRequest(epl request.EndpointLimit, method string, params, response interface{}, authenticated bool) error {
+ if authenticated && !d.Websocket.CanUseAuthenticatedEndpoints() {
+ return errWebsocketConnectionNotAuthenticated
+ }
+ input := &WsRequest{
+ JSONRPCVersion: rpcVersion,
+ ID: d.Websocket.Conn.GenerateMessageID(true),
+ Method: method,
+ Params: params,
+ }
+ resp := &wsResponse{Result: response}
+ err := d.sendWsPayload(epl, input, resp)
+ if err != nil {
+ return err
+ }
+ if resp.Error.Code != 0 || resp.Error.Message != "" {
+ var data string
+ if resp.Error.Data != nil {
+ value, err := json.Marshal(resp.Error.Data)
+ if err == nil {
+ data = string(value)
+ }
+ }
+ return fmt.Errorf("code: %d message: %s %s", resp.Error.Code, resp.Error.Message, data)
+ }
+ return nil
+}
+
+// sendWsPayload handles sending Websocket requests
+func (d *Deribit) sendWsPayload(ep request.EndpointLimit, input *WsRequest, response *wsResponse) error {
+ if input == nil {
+ return fmt.Errorf("%w, input can not be ", common.ErrNilPointer)
+ }
+ deadline := time.Now().Add(websocketRequestTimeout)
+ ctx, cancelFunc := context.WithDeadline(context.Background(), deadline)
+ defer func() {
+ if time.Now().After(deadline) {
+ cancelFunc()
+ }
+ }()
+ for attempt := 1; ; attempt++ {
+ // Initiate a rate limit reservation and sleep on requested endpoint
+ err := d.Requester.InitiateRateLimit(ctx, ep)
+ if err != nil {
+ return fmt.Errorf("failed to rate limit Websocket request: %w", err)
+ }
+
+ if d.Verbose {
+ log.Debugf(log.RequestSys, "%s attempt %d", d.Name, attempt)
+ }
+ var payload []byte
+ payload, err = d.Websocket.Conn.SendMessageReturnResponse(input.ID, input)
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(payload, response)
+ if err != nil {
+ return err
+ }
+ switch response.Error.Code {
+ case 10040:
+ after := 100 * time.Millisecond // because all the request rate will be reset after 1 sec interval
+ backoff := request.DefaultBackoff()(attempt)
+ delay := backoff
+ if after > backoff {
+ delay = after
+ }
+ if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(delay)) {
+ return errors.New("deadline would be exceeded by retry")
+ }
+
+ if d.Verbose {
+ log.Errorf(log.RequestSys,
+ "%s request has failed. Retrying request in %s, attempt %d",
+ d.Name,
+ delay,
+ attempt)
+ }
+ time.Sleep(delay)
+ continue
+ default:
+ return nil
+ }
+ }
+}
diff --git a/exchanges/deribit/deribit_wrapper.go b/exchanges/deribit/deribit_wrapper.go
new file mode 100644
index 00000000..3b3bef96
--- /dev/null
+++ b/exchanges/deribit/deribit_wrapper.go
@@ -0,0 +1,1601 @@
+package deribit
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/shopspring/decimal"
+ "github.com/thrasher-corp/gocryptotrader/common"
+ "github.com/thrasher-corp/gocryptotrader/common/key"
+ "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/deposit"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/futures"
+ "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/stream"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
+ "github.com/thrasher-corp/gocryptotrader/exchanges/trade"
+ "github.com/thrasher-corp/gocryptotrader/log"
+ "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
+)
+
+// GetDefaultConfig returns a default exchange config
+func (d *Deribit) GetDefaultConfig(ctx context.Context) (*config.Exchange, error) {
+ d.SetDefaults()
+ exchCfg, err := d.GetStandardConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ err = d.SetupDefaults(exchCfg)
+ if err != nil {
+ return nil, err
+ }
+ if d.Features.Supports.RESTCapabilities.AutoPairUpdates {
+ err := d.UpdateTradablePairs(ctx, true)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return exchCfg, nil
+}
+
+// SetDefaults sets the basic defaults for Deribit
+func (d *Deribit) SetDefaults() {
+ d.Name = "Deribit"
+ d.Enabled = true
+ d.Verbose = true
+ d.API.CredentialsValidator.RequiresKey = true
+ d.API.CredentialsValidator.RequiresSecret = true
+
+ requestFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
+ configFmt := ¤cy.PairFormat{Uppercase: true, Delimiter: currency.DashDelimiter}
+ err := d.StoreAssetPairFormat(asset.Spot, currency.PairStore{
+ RequestFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter},
+ ConfigFormat: ¤cy.PairFormat{Uppercase: true, Delimiter: currency.UnderscoreDelimiter}})
+ if err != nil {
+ log.Errorln(log.ExchangeSys, err)
+ }
+ for _, assetType := range []asset.Item{asset.Futures, asset.Options, asset.OptionCombo, asset.FutureCombo} {
+ if err = d.StoreAssetPairFormat(assetType, currency.PairStore{RequestFormat: requestFmt, ConfigFormat: configFmt}); err != nil {
+ log.Errorln(log.ExchangeSys, err)
+ }
+ }
+
+ // Fill out the capabilities/features that the exchange supports
+ d.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,
+ UserTradeHistory: true,
+ CryptoDeposit: true,
+ CryptoWithdrawal: true,
+ TradeFee: true,
+ CryptoWithdrawalFee: true,
+ MultiChainDeposits: true,
+ MultiChainWithdrawals: true,
+ },
+ WebsocketCapabilities: protocol.Features{
+ TickerFetching: true,
+ OrderbookFetching: true,
+ },
+ WithdrawPermissions: exchange.AutoWithdrawCrypto |
+ exchange.AutoWithdrawFiat,
+ Kline: kline.ExchangeCapabilitiesSupported{
+ Intervals: true,
+ },
+ FuturesCapabilities: exchange.FuturesCapabilities{
+ Positions: true,
+ Leverage: true,
+ FundingRates: true,
+ SupportedFundingRateFrequencies: map[kline.Interval]bool{
+ kline.OneHour: true,
+ kline.EightHour: true,
+ },
+ OpenInterest: exchange.OpenInterestSupport{
+ Supported: true,
+ },
+ },
+ },
+ Enabled: exchange.FeaturesEnabled{
+ AutoPairUpdates: true,
+ Kline: kline.ExchangeCapabilitiesEnabled{
+ Intervals: kline.DeployExchangeIntervals(
+ kline.IntervalCapacity{Interval: kline.HundredMilliseconds},
+ kline.IntervalCapacity{Interval: kline.OneMin},
+ kline.IntervalCapacity{Interval: kline.ThreeMin},
+ kline.IntervalCapacity{Interval: kline.FiveMin},
+ kline.IntervalCapacity{Interval: kline.TenMin},
+ kline.IntervalCapacity{Interval: kline.FifteenMin},
+ kline.IntervalCapacity{Interval: kline.ThirtyMin},
+ kline.IntervalCapacity{Interval: kline.OneHour},
+ kline.IntervalCapacity{Interval: kline.TwoHour},
+ // NOTE: The supported time intervals below are returned
+ // offset to +8 hours. This may lead to
+ // issues with candle quality and conversion as the
+ // intervals may be broken up. The below intervals
+ // are therefore constructed from the intervals above.
+ // kline.IntervalCapacity{Interval: kline.ThreeHour},
+ // kline.IntervalCapacity{Interval: kline.SixHour},
+ // kline.IntervalCapacity{Interval: kline.TwelveHour},
+ // kline.IntervalCapacity{Interval: kline.OneDay},
+ ),
+ GlobalResultLimit: 500,
+ },
+ },
+ }
+ d.Requester, err = request.New(d.Name,
+ common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout),
+ request.WithLimiter(SetRateLimit()),
+ )
+ if err != nil {
+ log.Errorln(log.ExchangeSys, err)
+ }
+ for _, assetType := range []asset.Item{asset.Options, asset.OptionCombo, asset.FutureCombo} {
+ if err = d.DisableAssetWebsocketSupport(assetType); err != nil {
+ log.Errorln(log.ExchangeSys, err)
+ }
+ }
+ d.API.Endpoints = d.NewEndpoints()
+ err = d.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{
+ exchange.RestFutures: "https://www.deribit.com",
+ exchange.RestSpot: "https://www.deribit.com",
+ exchange.RestSpotSupplementary: "https://test.deribit.com",
+ })
+ if err != nil {
+ log.Errorln(log.ExchangeSys, err)
+ }
+ d.Websocket = stream.NewWebsocket()
+ d.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
+ d.WebsocketResponseCheckTimeout = exchange.DefaultWebsocketResponseCheckTimeout
+ d.WebsocketOrderbookBufferLimit = exchange.DefaultWebsocketOrderbookBufferLimit
+}
+
+// Setup takes in the supplied exchange configuration details and sets params
+func (d *Deribit) Setup(exch *config.Exchange) error {
+ err := exch.Validate()
+ if err != nil {
+ return err
+ }
+ if !exch.Enabled {
+ d.SetEnabled(false)
+ return nil
+ }
+ err = d.SetupDefaults(exch)
+ if err != nil {
+ return err
+ }
+ err = d.Websocket.Setup(&stream.WebsocketSetup{
+ ExchangeConfig: exch,
+ DefaultURL: deribitWebsocketAddress,
+ RunningURL: deribitWebsocketAddress,
+ Connector: d.WsConnect,
+ Subscriber: d.Subscribe,
+ Unsubscriber: d.Unsubscribe,
+ GenerateSubscriptions: d.GenerateDefaultSubscriptions,
+ Features: &d.Features.Supports.WebsocketCapabilities,
+ OrderbookBufferConfig: buffer.Config{
+ SortBuffer: true,
+ SortBufferByUpdateIDs: true,
+ },
+ })
+ if err != nil {
+ return err
+ }
+ return d.Websocket.SetupNewConnection(stream.ConnectionSetup{
+ URL: d.Websocket.GetWebsocketURL(),
+ ResponseCheckTimeout: exch.WebsocketResponseCheckTimeout,
+ ResponseMaxLimit: exch.WebsocketResponseMaxLimit,
+ })
+}
+
+// FetchTradablePairs returns a list of the exchanges tradable pairs
+func (d *Deribit) FetchTradablePairs(ctx context.Context, assetType asset.Item) (currency.Pairs, error) {
+ if !d.SupportsAsset(assetType) {
+ return nil, fmt.Errorf("%s: %w - %v", d.Name, asset.ErrNotSupported, assetType)
+ }
+ var resp currency.Pairs
+ for _, x := range baseCurrencies {
+ var instrumentsData []InstrumentData
+ var err error
+ if d.Websocket.IsConnected() {
+ instrumentsData, err = d.WSRetrieveInstrumentsData(currency.NewCode(x), d.GetAssetKind(assetType), false)
+ } else {
+ instrumentsData, err = d.GetInstruments(ctx, currency.NewCode(x), d.GetAssetKind(assetType), false)
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ for y := range instrumentsData {
+ if !instrumentsData[y].IsActive {
+ continue
+ }
+ var cp currency.Pair
+ if assetType == asset.Options {
+ cp, err = currency.NewPairDelimiter(instrumentsData[y].InstrumentName, currency.DashDelimiter)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ cp, err = currency.NewPairFromString(instrumentsData[y].InstrumentName)
+ if err != nil {
+ return nil, err
+ }
+ }
+ resp = resp.Add(cp)
+ }
+ }
+ return resp, nil
+}
+
+// UpdateTradablePairs updates the exchanges available pairs and stores
+// them in the exchanges config
+func (d *Deribit) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error {
+ assets := d.GetAssetTypes(false)
+ for x := range assets {
+ pairs, err := d.FetchTradablePairs(ctx, assets[x])
+ if err != nil {
+ return err
+ }
+ err = d.UpdatePairs(pairs, assets[x], false, forceUpdate)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// UpdateTickers updates the ticker for all currency pairs of a given asset type
+func (d *Deribit) UpdateTickers(_ context.Context, _ asset.Item) error {
+ return common.ErrFunctionNotSupported
+}
+
+// UpdateTicker updates and returns the ticker for a currency pair
+func (d *Deribit) UpdateTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
+ if !d.SupportsAsset(assetType) {
+ return nil, fmt.Errorf("%s: %w - %s", d.Name, asset.ErrNotSupported, assetType)
+ }
+ p, err := d.FormatExchangeCurrency(p, assetType)
+ if err != nil {
+ return nil, err
+ }
+ instrumentID := d.formatPairString(assetType, p)
+ var tickerData *TickerData
+ if d.Websocket.IsConnected() {
+ tickerData, err = d.WSRetrievePublicTicker(instrumentID)
+ } else {
+ tickerData, err = d.GetPublicTicker(ctx, instrumentID)
+ }
+ if err != nil {
+ return nil, err
+ }
+ resp := ticker.Price{
+ ExchangeName: d.Name,
+ Pair: p,
+ AssetType: assetType,
+ Ask: tickerData.BestAskPrice,
+ AskSize: tickerData.BestAskAmount,
+ Bid: tickerData.BestBidPrice,
+ BidSize: tickerData.BestBidAmount,
+ High: tickerData.Stats.High,
+ Low: tickerData.Stats.Low,
+ Last: tickerData.LastPrice,
+ Volume: tickerData.Stats.Volume,
+ Close: tickerData.LastPrice,
+ IndexPrice: tickerData.IndexPrice,
+ MarkPrice: tickerData.MarkPrice,
+ QuoteVolume: tickerData.Stats.VolumeUSD,
+ }
+ err = ticker.ProcessTicker(&resp)
+ if err != nil {
+ return nil, err
+ }
+ return ticker.GetTicker(d.Name, p, assetType)
+}
+
+// FetchTicker returns the ticker for a currency pair
+func (d *Deribit) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
+ tickerNew, err := ticker.GetTicker(d.Name, p, assetType)
+ if err != nil {
+ return d.UpdateTicker(ctx, p, assetType)
+ }
+ return tickerNew, nil
+}
+
+// FetchOrderbook returns orderbook base on the currency pair
+func (d *Deribit) FetchOrderbook(ctx context.Context, currencyPair currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
+ ob, err := orderbook.Get(d.Name, currencyPair, assetType)
+ if err != nil {
+ return d.UpdateOrderbook(ctx, currencyPair, assetType)
+ }
+ return ob, nil
+}
+
+// UpdateOrderbook updates and returns the orderbook for a currency pair
+func (d *Deribit) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) {
+ p, err := d.FormatExchangeCurrency(p, assetType)
+ if err != nil {
+ return nil, err
+ }
+ instrumentID := d.formatPairString(assetType, p)
+ var obData *Orderbook
+ if d.Websocket.IsConnected() {
+ obData, err = d.WSRetrieveOrderbookData(instrumentID, 50)
+ } else {
+ obData, err = d.GetOrderbook(ctx, instrumentID, 50)
+ }
+ if err != nil {
+ return nil, err
+ }
+ book := &orderbook.Base{
+ Exchange: d.Name,
+ Pair: p,
+ Asset: assetType,
+ VerifyOrderbook: d.CanVerifyOrderbook,
+ }
+ book.Asks = make(orderbook.Tranches, 0, len(obData.Asks))
+ for x := range obData.Asks {
+ if obData.Asks[x][0] == 0 || obData.Asks[x][1] == 0 {
+ continue
+ }
+ book.Asks = append(book.Asks, orderbook.Tranche{
+ Price: obData.Asks[x][0],
+ Amount: obData.Asks[x][1],
+ })
+ }
+ book.Bids = make(orderbook.Tranches, 0, len(obData.Bids))
+ for x := range obData.Bids {
+ if obData.Bids[x][0] == 0 || obData.Bids[x][1] == 0 {
+ continue
+ }
+ book.Bids = append(book.Bids, orderbook.Tranche{
+ Price: obData.Bids[x][0],
+ Amount: obData.Bids[x][1],
+ })
+ }
+ err = book.Process()
+ if err != nil {
+ return book, err
+ }
+ return orderbook.Get(d.Name, p, assetType)
+}
+
+// UpdateAccountInfo retrieves balances for all enabled currencies
+func (d *Deribit) UpdateAccountInfo(ctx context.Context, _ asset.Item) (account.Holdings, error) {
+ var resp account.Holdings
+ resp.Exchange = d.Name
+ currencies, err := d.GetCurrencies(ctx)
+ if err != nil {
+ return resp, err
+ }
+ resp.Accounts = make([]account.SubAccount, len(currencies))
+ for x := range currencies {
+ var data *AccountSummaryData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ data, err = d.WSRetrieveAccountSummary(currency.NewCode(currencies[x].Currency), false)
+ } else {
+ data, err = d.GetAccountSummary(ctx, currency.NewCode(currencies[x].Currency), false)
+ }
+ if err != nil {
+ return resp, err
+ }
+ var subAcc account.SubAccount
+ subAcc.Currencies = append(subAcc.Currencies, account.Balance{
+ Currency: currency.NewCode(currencies[x].Currency),
+ Total: data.Balance,
+ Hold: data.Balance - data.AvailableFunds,
+ })
+ resp.Accounts[x] = subAcc
+ }
+ return resp, nil
+}
+
+// FetchAccountInfo retrieves balances for all enabled currencies
+func (d *Deribit) FetchAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) {
+ creds, err := d.GetCredentials(ctx)
+ if err != nil {
+ return account.Holdings{}, err
+ }
+ accountData, err := account.GetHoldings(d.Name, creds, assetType)
+ if err != nil {
+ return d.UpdateAccountInfo(ctx, assetType)
+ }
+ return accountData, nil
+}
+
+// GetAccountFundingHistory returns funding history, deposits and withdrawals
+func (d *Deribit) GetAccountFundingHistory(ctx context.Context) ([]exchange.FundingHistory, error) {
+ var currencies []CurrencyData
+ var err error
+ if d.Websocket.IsConnected() {
+ currencies, err = d.WSRetrieveCurrencies()
+ } else {
+ currencies, err = d.GetCurrencies(ctx)
+ }
+ if err != nil {
+ return nil, err
+ }
+ var resp []exchange.FundingHistory
+ for x := range currencies {
+ var deposits *DepositsData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ deposits, err = d.WSRetrieveDeposits(currency.NewCode(currencies[x].Currency), 100, 0)
+ } else {
+ deposits, err = d.GetDeposits(ctx, currency.NewCode(currencies[x].Currency), 100, 0)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for y := range deposits.Data {
+ resp = append(resp, exchange.FundingHistory{
+ ExchangeName: d.Name,
+ Status: deposits.Data[y].State,
+ TransferID: deposits.Data[y].TransactionID,
+ Timestamp: deposits.Data[y].UpdatedTimestamp.Time(),
+ Currency: currencies[x].Currency,
+ Amount: deposits.Data[y].Amount,
+ CryptoToAddress: deposits.Data[y].Address,
+ TransferType: "deposit",
+ })
+ }
+ var withdrawalData *WithdrawalsData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ withdrawalData, err = d.WSRetrieveWithdrawals(currency.NewCode(currencies[x].Currency), 100, 0)
+ } else {
+ withdrawalData, err = d.GetWithdrawals(ctx, currency.NewCode(currencies[x].Currency), 100, 0)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for z := range withdrawalData.Data {
+ resp = append(resp, exchange.FundingHistory{
+ ExchangeName: d.Name,
+ Status: withdrawalData.Data[z].State,
+ TransferID: withdrawalData.Data[z].TransactionID,
+ Timestamp: withdrawalData.Data[z].UpdatedTimestamp.Time(),
+ Currency: currencies[x].Currency,
+ Amount: withdrawalData.Data[z].Amount,
+ CryptoToAddress: withdrawalData.Data[z].Address,
+ TransferType: "withdrawal",
+ })
+ }
+ }
+ return resp, nil
+}
+
+// GetWithdrawalsHistory returns previous withdrawals data
+func (d *Deribit) GetWithdrawalsHistory(ctx context.Context, c currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) {
+ var currencies []CurrencyData
+ var err error
+ if d.Websocket.IsConnected() {
+ currencies, err = d.WSRetrieveCurrencies()
+ } else {
+ currencies, err = d.GetCurrencies(ctx)
+ }
+ if err != nil {
+ return nil, err
+ }
+ resp := []exchange.WithdrawalHistory{}
+ for x := range currencies {
+ if !strings.EqualFold(currencies[x].Currency, c.String()) {
+ continue
+ }
+ var withdrawalData *WithdrawalsData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ withdrawalData, err = d.WSRetrieveWithdrawals(currency.NewCode(currencies[x].Currency), 100, 0)
+ } else {
+ withdrawalData, err = d.GetWithdrawals(ctx, currency.NewCode(currencies[x].Currency), 100, 0)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for y := range withdrawalData.Data {
+ resp = append(resp, exchange.WithdrawalHistory{
+ Status: withdrawalData.Data[y].State,
+ TransferID: withdrawalData.Data[y].TransactionID,
+ Timestamp: withdrawalData.Data[y].UpdatedTimestamp.Time(),
+ Currency: currencies[x].Currency,
+ Amount: withdrawalData.Data[y].Amount,
+ CryptoToAddress: withdrawalData.Data[y].Address,
+ TransferType: "deposit",
+ })
+ }
+ }
+ return resp, nil
+}
+
+// GetRecentTrades returns the most recent trades for a currency and asset
+func (d *Deribit) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) {
+ if !d.SupportsAsset(assetType) {
+ return nil, fmt.Errorf("%s: %w - %s", d.Name, asset.ErrNotSupported, assetType)
+ }
+ p, err := d.FormatExchangeCurrency(p, assetType)
+ if err != nil {
+ return nil, err
+ }
+ instrumentID := d.formatPairString(assetType, p)
+ resp := []trade.Data{}
+ var trades *PublicTradesData
+ if d.Websocket.IsConnected() {
+ trades, err = d.WSRetrieveLastTradesByInstrument(
+ instrumentID, "", "", "", 0, false)
+ } else {
+ trades, err = d.GetLastTradesByInstrument(
+ ctx,
+ instrumentID, "", "", "", 0, false)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for a := range trades.Trades {
+ sideData := order.Sell
+ if trades.Trades[a].Direction == sideBUY {
+ sideData = order.Buy
+ }
+ resp = append(resp, trade.Data{
+ TID: trades.Trades[a].TradeID,
+ Exchange: d.Name,
+ Price: trades.Trades[a].Price,
+ Amount: trades.Trades[a].Amount,
+ Timestamp: trades.Trades[a].Timestamp.Time(),
+ AssetType: assetType,
+ Side: sideData,
+ CurrencyPair: p,
+ })
+ }
+ return resp, nil
+}
+
+// GetHistoricTrades returns historic trade data within the timeframe provided
+func (d *Deribit) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, timestampStart, timestampEnd time.Time) ([]trade.Data, error) {
+ if common.StartEndTimeCheck(timestampStart, timestampEnd) != nil {
+ return nil, fmt.Errorf("invalid time range supplied. Start: %v End %v",
+ timestampStart,
+ timestampEnd)
+ }
+ p, err := d.FormatExchangeCurrency(p, assetType)
+ if err != nil {
+ return nil, err
+ }
+ var instrumentID string
+ switch assetType {
+ case asset.Futures, asset.Options, asset.Spot:
+ instrumentID = d.formatPairString(assetType, p)
+ default:
+ return nil, fmt.Errorf("%w asset type %v", asset.ErrNotSupported, assetType)
+ }
+ var resp []trade.Data
+ var tradesData *PublicTradesData
+ var hasMore = true
+ for hasMore {
+ if d.Websocket.IsConnected() {
+ tradesData, err = d.WSRetrieveLastTradesByInstrumentAndTime(instrumentID, "asc", 100, true, timestampStart, timestampEnd)
+ } else {
+ tradesData, err = d.GetLastTradesByInstrumentAndTime(ctx, instrumentID, "asc", 100, timestampStart, timestampEnd)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if len(tradesData.Trades) != 100 {
+ hasMore = false
+ }
+ for t := range tradesData.Trades {
+ if t == 99 {
+ if timestampStart.Equal(tradesData.Trades[t].Timestamp.Time()) {
+ hasMore = false
+ }
+ timestampStart = tradesData.Trades[t].Timestamp.Time()
+ }
+ sideData := order.Sell
+ if tradesData.Trades[t].Direction == sideBUY {
+ sideData = order.Buy
+ }
+ resp = append(resp, trade.Data{
+ TID: tradesData.Trades[t].TradeID,
+ Exchange: d.Name,
+ Price: tradesData.Trades[t].Price,
+ Amount: tradesData.Trades[t].Amount,
+ Timestamp: tradesData.Trades[t].Timestamp.Time(),
+ AssetType: assetType,
+ Side: sideData,
+ CurrencyPair: p,
+ })
+ }
+ }
+ return resp, nil
+}
+
+// SubmitOrder submits a new order
+func (d *Deribit) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) {
+ err := s.Validate()
+ if err != nil {
+ return nil, err
+ }
+ if !d.SupportsAsset(s.AssetType) {
+ return nil, fmt.Errorf("%s: orderType %v is not valid", d.Name, s.AssetType)
+ }
+ var orderID string
+ var fmtPair currency.Pair
+ status := order.New
+ fmtPair, err = d.FormatExchangeCurrency(s.Pair, s.AssetType)
+ if err != nil {
+ return nil, err
+ }
+ timeInForce := ""
+ if s.ImmediateOrCancel {
+ timeInForce = "immediate_or_cancel"
+ }
+ var data *PrivateTradeData
+ reqParams := &OrderBuyAndSellParams{
+ Instrument: fmtPair.String(),
+ OrderType: strings.ToLower(s.Type.String()),
+ Label: s.ClientOrderID,
+ TimeInForce: timeInForce,
+ Amount: s.Amount,
+ Price: s.Price,
+ TriggerPrice: s.TriggerPrice,
+ PostOnly: s.PostOnly,
+ ReduceOnly: s.ReduceOnly,
+ }
+ switch {
+ case s.Side.IsLong():
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ data, err = d.WSSubmitBuy(reqParams)
+ } else {
+ data, err = d.SubmitBuy(ctx, reqParams)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if data == nil {
+ return nil, common.ErrNoResponse
+ }
+ orderID = data.Order.OrderID
+ case s.Side.IsShort():
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ data, err = d.WSSubmitSell(reqParams)
+ } else {
+ data, err = d.SubmitSell(ctx, reqParams)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if data == nil {
+ return nil, common.ErrNoResponse
+ }
+ orderID = data.Order.OrderID
+ }
+ resp, err := s.DeriveSubmitResponse(orderID)
+ if err != nil {
+ return nil, err
+ }
+ resp.Status = status
+ return resp, nil
+}
+
+// ModifyOrder will allow of changing orderbook placement and limit to
+// market conversion
+func (d *Deribit) ModifyOrder(ctx context.Context, action *order.Modify) (*order.ModifyResponse, error) {
+ if err := action.Validate(); err != nil {
+ return nil, err
+ }
+ if !d.SupportsAsset(action.AssetType) {
+ return nil, fmt.Errorf("%s: %w - %v", d.Name, asset.ErrNotSupported, action.AssetType)
+ }
+ var modify *PrivateTradeData
+ var err error
+ reqParam := &OrderBuyAndSellParams{
+ TriggerPrice: action.TriggerPrice,
+ PostOnly: action.PostOnly,
+ Amount: action.Amount,
+ OrderID: action.OrderID,
+ Price: action.Price,
+ }
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ modify, err = d.WSSubmitEdit(reqParam)
+ } else {
+ modify, err = d.SubmitEdit(ctx, reqParam)
+ }
+ if err != nil {
+ return nil, err
+ }
+ resp, err := action.DeriveModifyResponse()
+ if err != nil {
+ return nil, err
+ }
+ resp.OrderID = modify.Order.OrderID
+ return resp, nil
+}
+
+// CancelOrder cancels an order by its corresponding ID number
+func (d *Deribit) CancelOrder(ctx context.Context, ord *order.Cancel) error {
+ if !d.SupportsAsset(ord.AssetType) {
+ return fmt.Errorf("%s: %w - %s", d.Name, asset.ErrNotSupported, ord.AssetType)
+ }
+ err := ord.Validate(ord.StandardCancel())
+ if err != nil {
+ return err
+ }
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ _, err = d.WSSubmitCancel(ord.OrderID)
+ } else {
+ _, err = d.SubmitCancel(ctx, ord.OrderID)
+ }
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+// CancelBatchOrders cancels orders by their corresponding ID numbers
+func (d *Deribit) CancelBatchOrders(_ context.Context, _ []order.Cancel) (*order.CancelBatchResponse, error) {
+ return nil, common.ErrFunctionNotSupported
+}
+
+// CancelAllOrders cancels all orders associated with a currency pair
+func (d *Deribit) CancelAllOrders(ctx context.Context, orderCancellation *order.Cancel) (order.CancelAllResponse, error) {
+ if err := orderCancellation.Validate(); err != nil {
+ return order.CancelAllResponse{}, err
+ }
+ var cancelData *MultipleCancelResponse
+ pairFmt, err := d.GetPairFormat(orderCancellation.AssetType, true)
+ if err != nil {
+ return order.CancelAllResponse{}, err
+ }
+ var orderTypeStr string
+ switch orderCancellation.Type {
+ case order.Limit:
+ orderTypeStr = order.Limit.String()
+ case order.Market:
+ orderTypeStr = order.Market.String()
+ case order.AnyType, order.UnknownType:
+ orderTypeStr = "all"
+ default:
+ return order.CancelAllResponse{}, fmt.Errorf("%s: orderType %v is not valid", d.Name, orderCancellation.Type)
+ }
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ cancelData, err = d.WSSubmitCancelAllByInstrument(pairFmt.Format(orderCancellation.Pair), orderTypeStr, true, true)
+ } else {
+ cancelData, err = d.SubmitCancelAllByInstrument(ctx, pairFmt.Format(orderCancellation.Pair), orderTypeStr, true, true)
+ }
+ if err != nil {
+ return order.CancelAllResponse{}, err
+ }
+ response := order.CancelAllResponse{Count: cancelData.CancelCount}
+ if len(cancelData.CancelDetails) > 0 {
+ response.Status = make(map[string]string)
+ for a := range cancelData.CancelDetails {
+ for b := range cancelData.CancelDetails[a].Result {
+ response.Status[cancelData.CancelDetails[a].Result[b].OrderID] = cancelData.CancelDetails[a].Result[b].OrderState
+ }
+ }
+ }
+ return response, nil
+}
+
+// GetOrderInfo returns order information based on order ID
+func (d *Deribit) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pair, assetType asset.Item) (*order.Detail, error) {
+ if !d.SupportsAsset(assetType) {
+ return nil, fmt.Errorf("%w assetType %v", asset.ErrNotSupported, assetType)
+ }
+ var orderInfo *OrderData
+ var err error
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ orderInfo, err = d.WSRetrievesOrderState(orderID)
+ } else {
+ orderInfo, err = d.GetOrderState(ctx, orderID)
+ }
+ if err != nil {
+ return nil, err
+ }
+ orderSide := order.Sell
+ if orderInfo.Direction == sideBUY {
+ orderSide = order.Buy
+ }
+ orderType, err := order.StringToOrderType(orderInfo.OrderType)
+ if err != nil {
+ return nil, err
+ }
+ var pair currency.Pair
+ pair, err = currency.NewPairFromString(orderInfo.InstrumentName)
+ if err != nil {
+ return nil, err
+ }
+ var orderStatus order.Status
+ if orderInfo.OrderState == "untriggered" {
+ orderStatus = order.UnknownStatus
+ } else {
+ orderStatus, err = order.StringToOrderStatus(orderInfo.OrderState)
+ if err != nil {
+ return nil, fmt.Errorf("%v: orderStatus %s not supported", d.Name, orderInfo.OrderState)
+ }
+ }
+ return &order.Detail{
+ AssetType: assetType,
+ Exchange: d.Name,
+ PostOnly: orderInfo.PostOnly,
+ Price: orderInfo.Price,
+ Amount: orderInfo.Amount,
+ ExecutedAmount: orderInfo.FilledAmount,
+ Fee: orderInfo.Commission,
+ RemainingAmount: orderInfo.Amount - orderInfo.FilledAmount,
+ OrderID: orderInfo.OrderID,
+ Pair: pair,
+ LastUpdated: orderInfo.LastUpdateTimestamp.Time(),
+ Side: orderSide,
+ Type: orderType,
+ Status: orderStatus,
+ }, nil
+}
+
+// GetDepositAddress returns a deposit address for a specified currency
+func (d *Deribit) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, _ string) (*deposit.Address, error) {
+ var addressData *DepositAddressData
+ var err error
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ addressData, err = d.WSRetrieveCurrentDepositAddress(cryptocurrency)
+ } else {
+ addressData, err = d.GetCurrentDepositAddress(ctx, cryptocurrency)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &deposit.Address{
+ Address: addressData.Address,
+ Chain: addressData.Currency,
+ }, nil
+}
+
+// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
+// submitted
+func (d *Deribit) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
+ err := withdrawRequest.Validate()
+ if err != nil {
+ return nil, err
+ }
+ var withdrawData *WithdrawData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ withdrawData, err = d.WSSubmitWithdraw(withdrawRequest.Currency, withdrawRequest.Crypto.Address, "", withdrawRequest.Amount)
+ } else {
+ withdrawData, err = d.SubmitWithdraw(ctx, withdrawRequest.Currency, withdrawRequest.Crypto.Address, "", withdrawRequest.Amount)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return &withdraw.ExchangeResponse{
+ ID: strconv.FormatInt(withdrawData.ID, 10),
+ Status: withdrawData.State,
+ }, err
+}
+
+// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is submitted
+func (d *Deribit) WithdrawFiatFunds(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
+ return nil, common.ErrFunctionNotSupported
+}
+
+// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is submitted
+func (d *Deribit) WithdrawFiatFundsToInternationalBank(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
+ return nil, common.ErrFunctionNotSupported
+}
+
+// GetActiveOrders retrieves any orders that are active/open
+func (d *Deribit) GetActiveOrders(ctx context.Context, getOrdersRequest *order.MultiOrderRequest) (order.FilteredOrders, error) {
+ if err := getOrdersRequest.Validate(); err != nil {
+ return nil, err
+ }
+ if !d.SupportsAsset(getOrdersRequest.AssetType) {
+ return nil, fmt.Errorf("%s: %w - %v", d.Name, asset.ErrNotSupported, getOrdersRequest.AssetType)
+ }
+ if len(getOrdersRequest.Pairs) == 0 {
+ return nil, currency.ErrCurrencyPairsEmpty
+ }
+ var resp = []order.Detail{}
+ for x := range getOrdersRequest.Pairs {
+ fmtPair, err := d.FormatExchangeCurrency(getOrdersRequest.Pairs[x], getOrdersRequest.AssetType)
+ if err != nil {
+ return nil, err
+ }
+ var oTypeString string
+ switch getOrdersRequest.Type {
+ case order.AnyType, order.UnknownType:
+ oTypeString = "all"
+ default:
+ oTypeString = getOrdersRequest.Type.Lower()
+ }
+ var ordersData []OrderData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ ordersData, err = d.WSRetrieveOpenOrdersByInstrument(fmtPair.String(), oTypeString)
+ } else {
+ ordersData, err = d.GetOpenOrdersByInstrument(ctx, fmtPair.String(), oTypeString)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for y := range ordersData {
+ orderSide := order.Sell
+ if ordersData[y].Direction == sideBUY {
+ orderSide = order.Buy
+ }
+ if getOrdersRequest.Side != orderSide && getOrdersRequest.Side != order.AnySide {
+ continue
+ }
+ orderType, err := order.StringToOrderType(ordersData[y].OrderType)
+ if err != nil {
+ return nil, err
+ }
+ if getOrdersRequest.Type != orderType && getOrdersRequest.Type != order.AnyType {
+ continue
+ }
+ var orderStatus order.Status
+ ordersData[y].OrderState = strings.ToLower(ordersData[y].OrderState)
+ if ordersData[y].OrderState != "open" {
+ continue
+ }
+ resp = append(resp, order.Detail{
+ AssetType: getOrdersRequest.AssetType,
+ Exchange: d.Name,
+ PostOnly: ordersData[y].PostOnly,
+ Price: ordersData[y].Price,
+ Amount: ordersData[y].Amount,
+ ExecutedAmount: ordersData[y].FilledAmount,
+ Fee: ordersData[y].Commission,
+ RemainingAmount: ordersData[y].Amount - ordersData[y].FilledAmount,
+ OrderID: ordersData[y].OrderID,
+ Pair: getOrdersRequest.Pairs[x],
+ LastUpdated: ordersData[y].LastUpdateTimestamp.Time(),
+ Side: orderSide,
+ Type: orderType,
+ Status: orderStatus,
+ })
+ }
+ }
+ return resp, nil
+}
+
+// GetOrderHistory retrieves account order information
+// Can Limit response to specific order status
+func (d *Deribit) GetOrderHistory(ctx context.Context, getOrdersRequest *order.MultiOrderRequest) (order.FilteredOrders, error) {
+ if err := getOrdersRequest.Validate(); err != nil {
+ return nil, err
+ }
+ if len(getOrdersRequest.Pairs) == 0 {
+ return nil, currency.ErrCurrencyPairsEmpty
+ }
+ var resp []order.Detail
+ for x := range getOrdersRequest.Pairs {
+ fmtPair, err := d.FormatExchangeCurrency(getOrdersRequest.Pairs[x], getOrdersRequest.AssetType)
+ if err != nil {
+ return nil, err
+ }
+ var ordersData []OrderData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ ordersData, err = d.WSRetrieveOrderHistoryByInstrument(fmtPair.String(), 100, 0, true, true)
+ } else {
+ ordersData, err = d.GetOrderHistoryByInstrument(ctx, fmtPair.String(), 100, 0, true, true)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for y := range ordersData {
+ orderSide := order.Sell
+ if ordersData[y].Direction == sideBUY {
+ orderSide = order.Buy
+ }
+ if getOrdersRequest.Side != orderSide && getOrdersRequest.Side != order.AnySide {
+ continue
+ }
+ orderType, err := order.StringToOrderType(ordersData[y].OrderType)
+ if err != nil {
+ return nil, err
+ }
+ if getOrdersRequest.Type != orderType && getOrdersRequest.Type != order.AnyType {
+ continue
+ }
+ var orderStatus order.Status
+ if ordersData[y].OrderState == "untriggered" {
+ orderStatus = order.UnknownStatus
+ } else {
+ orderStatus, err = order.StringToOrderStatus(ordersData[y].OrderState)
+ if err != nil {
+ return resp, fmt.Errorf("%v: orderStatus %s not supported", d.Name, ordersData[y].OrderState)
+ }
+ }
+ resp = append(resp, order.Detail{
+ AssetType: getOrdersRequest.AssetType,
+ Exchange: d.Name,
+ PostOnly: ordersData[y].PostOnly,
+ Price: ordersData[y].Price,
+ Amount: ordersData[y].Amount,
+ ExecutedAmount: ordersData[y].FilledAmount,
+ Fee: ordersData[y].Commission,
+ RemainingAmount: ordersData[y].Amount - ordersData[y].FilledAmount,
+ OrderID: ordersData[y].OrderID,
+ Pair: getOrdersRequest.Pairs[x],
+ LastUpdated: ordersData[y].LastUpdateTimestamp.Time(),
+ Side: orderSide,
+ Type: orderType,
+ Status: orderStatus,
+ })
+ }
+ }
+ return resp, nil
+}
+
+// GetFeeByType returns an estimate of fee based on the type of transaction
+func (d *Deribit) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
+ if feeBuilder == nil {
+ return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer)
+ }
+ if !d.AreCredentialsValid(ctx) && // Todo check connection status
+ feeBuilder.FeeType == exchange.CryptocurrencyTradeFee {
+ feeBuilder.FeeType = exchange.OfflineTradeFee
+ }
+ var fee float64
+ var err error
+ switch feeBuilder.FeeType {
+ case exchange.CryptocurrencyTradeFee:
+ fee, err = calculateTradingFee(feeBuilder)
+ if err != nil {
+ return 0, err
+ }
+ case exchange.CryptocurrencyDepositFee:
+ case exchange.CryptocurrencyWithdrawalFee:
+ // Withdrawals are processed instantly if the balance in our hot wallet permits so. We keep only a small percentage of coins in hot storage,
+ // therefore there is a chance that your withdrawal cannot be processed immediately. If needed, once a day we will replenish the balance of the hot wallet from the cold storage.
+ case exchange.OfflineTradeFee:
+ fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount)
+ }
+ if fee < 0 {
+ fee = 0
+ }
+ return fee, nil
+}
+
+// ValidateAPICredentials validates current credentials used for wrapper
+// functionality
+func (d *Deribit) ValidateAPICredentials(ctx context.Context, assetType asset.Item) error {
+ _, err := d.UpdateAccountInfo(ctx, assetType)
+ return d.CheckTransientError(err)
+}
+
+// GetHistoricCandles returns candles between a time period for a set time interval
+func (d *Deribit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
+ req, err := d.GetKlineRequest(pair, a, interval, start, end, false)
+ if err != nil {
+ return nil, err
+ }
+ intervalString, err := d.GetResolutionFromInterval(interval)
+ if err != nil {
+ return nil, err
+ }
+ switch a {
+ case asset.Futures, asset.Spot:
+ var tradingViewData *TVChartData
+ if d.Websocket.IsConnected() {
+ tradingViewData, err = d.WSRetrievesTradingViewChartData(d.formatFuturesTradablePair(req.RequestFormatted), intervalString, start, end)
+ } else {
+ tradingViewData, err = d.GetTradingViewChart(ctx, d.formatFuturesTradablePair(req.RequestFormatted), intervalString, start, end)
+ }
+ if err != nil {
+ return nil, err
+ } else if len(tradingViewData.Ticks) == 0 {
+ return nil, kline.ErrNoTimeSeriesDataToConvert
+ }
+ checkLen := len(tradingViewData.Ticks)
+ if len(tradingViewData.Open) != checkLen ||
+ len(tradingViewData.High) != checkLen ||
+ len(tradingViewData.Low) != checkLen ||
+ len(tradingViewData.Close) != checkLen ||
+ len(tradingViewData.Volume) != checkLen {
+ return nil, fmt.Errorf("%s - %v: invalid trading view chart data received", a, req.RequestFormatted)
+ }
+ listCandles := make([]kline.Candle, 0, len(tradingViewData.Ticks))
+ for x := range tradingViewData.Ticks {
+ timeInfo := time.UnixMilli(tradingViewData.Ticks[x]).UTC()
+ if timeInfo.Before(start) {
+ continue
+ }
+ listCandles = append(listCandles, kline.Candle{
+ Open: tradingViewData.Open[x],
+ High: tradingViewData.High[x],
+ Low: tradingViewData.Low[x],
+ Close: tradingViewData.Close[x],
+ Volume: tradingViewData.Volume[x],
+ Time: timeInfo,
+ })
+ }
+ return req.ProcessResponse(listCandles)
+ case asset.OptionCombo, asset.FutureCombo, asset.Options:
+ // TODO: candlestick data for asset item option_combo, future_combo, and option not supported yet
+ }
+ return nil, fmt.Errorf("%w candlestick data for asset type %v", asset.ErrNotSupported, a)
+}
+
+// GetHistoricCandlesExtended returns candles between a time period for a set time interval
+func (d *Deribit) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) {
+ req, err := d.GetKlineExtendedRequest(pair, a, interval, start, end)
+ if err != nil {
+ return nil, err
+ }
+ var tradingViewData *TVChartData
+ timeSeries := make([]kline.Candle, 0, req.Size())
+ switch a {
+ case asset.Futures, asset.Spot:
+ for x := range req.RangeHolder.Ranges {
+ intervalString, err := d.GetResolutionFromInterval(interval)
+ if err != nil {
+ return nil, err
+ }
+ if d.Websocket.IsConnected() {
+ tradingViewData, err = d.WSRetrievesTradingViewChartData(d.formatFuturesTradablePair(req.RequestFormatted), intervalString, req.RangeHolder.Ranges[x].Start.Time, req.RangeHolder.Ranges[x].End.Time)
+ } else {
+ tradingViewData, err = d.GetTradingViewChart(ctx, d.formatFuturesTradablePair(req.RequestFormatted), intervalString, req.RangeHolder.Ranges[x].Start.Time, req.RangeHolder.Ranges[x].End.Time)
+ }
+ if err != nil {
+ return nil, err
+ }
+ checkLen := len(tradingViewData.Ticks)
+ if len(tradingViewData.Open) != checkLen ||
+ len(tradingViewData.High) != checkLen ||
+ len(tradingViewData.Low) != checkLen ||
+ len(tradingViewData.Close) != checkLen ||
+ len(tradingViewData.Volume) != checkLen {
+ return nil, fmt.Errorf("%s - %v: invalid trading view chart data received", a, d.formatFuturesTradablePair(req.RequestFormatted))
+ }
+ for i := range tradingViewData.Ticks {
+ timeInfo := time.UnixMilli(tradingViewData.Ticks[i]).UTC()
+ if timeInfo.Before(start) {
+ continue
+ }
+ timeSeries = append(timeSeries, kline.Candle{
+ Open: tradingViewData.Open[i],
+ High: tradingViewData.High[i],
+ Low: tradingViewData.Low[i],
+ Close: tradingViewData.Close[i],
+ Volume: tradingViewData.Volume[i],
+ Time: timeInfo,
+ })
+ }
+ }
+ return req.ProcessResponse(timeSeries)
+ case asset.OptionCombo, asset.FutureCombo, asset.Options:
+ // TODO: candlestick data for asset item option_combo, future_combo, and option not supported yet
+ }
+ return nil, fmt.Errorf("%w candlestick data for asset type %v", asset.ErrNotSupported, a)
+}
+
+// GetServerTime returns the current exchange server time.
+func (d *Deribit) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) {
+ return d.GetTime(ctx)
+}
+
+// AuthenticateWebsocket sends an authentication message to the websocket
+func (d *Deribit) AuthenticateWebsocket(ctx context.Context) error {
+ return d.wsLogin(ctx)
+}
+
+// GetFuturesContractDetails returns all contracts from the exchange by asset type
+func (d *Deribit) GetFuturesContractDetails(ctx context.Context, item asset.Item) ([]futures.Contract, error) {
+ if !item.IsFutures() {
+ return nil, futures.ErrNotFuturesAsset
+ }
+ if item != asset.Futures {
+ return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, item)
+ }
+ resp := []futures.Contract{}
+ for _, ccy := range baseCurrencies {
+ var marketSummary []InstrumentData
+ var err error
+ if d.Websocket.IsConnected() {
+ marketSummary, err = d.WSRetrieveInstrumentsData(currency.NewCode(ccy), d.GetAssetKind(item), false)
+ } else {
+ marketSummary, err = d.GetInstruments(ctx, currency.NewCode(ccy), d.GetAssetKind(item), false)
+ }
+ if err != nil {
+ return nil, err
+ }
+ for i := range marketSummary {
+ if marketSummary[i].Kind != "future" && marketSummary[i].Kind != "future_combo" {
+ continue
+ }
+ var cp currency.Pair
+ cp, err = currency.NewPairFromString(marketSummary[i].InstrumentName)
+ if err != nil {
+ return nil, err
+ }
+ var ct futures.ContractType
+ switch marketSummary[i].SettlementPeriod {
+ case "day":
+ ct = futures.Daily
+ case "week":
+ ct = futures.Weekly
+ case "month":
+ ct = futures.Monthly
+ case "perpetual":
+ ct = futures.Perpetual
+ }
+ var contractSettlementType futures.ContractSettlementType
+ if marketSummary[i].InstrumentType == "reversed" {
+ contractSettlementType = futures.Inverse
+ } else {
+ contractSettlementType = futures.Linear
+ }
+ resp = append(resp, futures.Contract{
+ Exchange: d.Name,
+ Name: cp,
+ Underlying: currency.NewPair(currency.NewCode(marketSummary[i].BaseCurrency), currency.NewCode(marketSummary[i].QuoteCurrency)),
+ Asset: item,
+ SettlementCurrencies: []currency.Code{currency.NewCode(marketSummary[i].SettlementCurrency)},
+ StartDate: marketSummary[i].CreationTimestamp.Time(),
+ EndDate: marketSummary[i].ExpirationTimestamp.Time(),
+ Type: ct,
+ SettlementType: contractSettlementType,
+ IsActive: marketSummary[i].IsActive,
+ MaxLeverage: marketSummary[i].MaxLeverage,
+ Multiplier: marketSummary[i].ContractSize,
+ })
+ }
+ }
+ return resp, nil
+}
+
+// GetLatestFundingRates returns the latest funding rates data
+func (d *Deribit) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) {
+ if r == nil {
+ return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer)
+ }
+ if !d.SupportsAsset(r.Asset) {
+ return nil, fmt.Errorf("%s %w", r.Asset, asset.ErrNotSupported)
+ }
+ isPerpetual, err := d.IsPerpetualFutureCurrency(r.Asset, r.Pair)
+ if !isPerpetual || err != nil {
+ return nil, futures.ErrNotPerpetualFuture
+ }
+ available, err := d.GetAvailablePairs(r.Asset)
+ if err != nil {
+ return nil, err
+ }
+ if !available.Contains(r.Pair, true) && r.Pair.Quote.String() != "PERPETUAL" && !strings.HasSuffix(r.Pair.String(), "PERP") {
+ return nil, fmt.Errorf("%w pair: %v", futures.ErrNotPerpetualFuture, r.Pair)
+ }
+ r.Pair, err = d.FormatExchangeCurrency(r.Pair, r.Asset)
+ if err != nil {
+ return nil, err
+ }
+ var fri []FundingRateHistory
+ fri, err = d.GetFundingRateHistory(ctx, r.Pair.String(), time.Now().Add(-time.Hour*16), time.Now())
+ if err != nil {
+ return nil, err
+ }
+
+ resp := make([]fundingrate.LatestRateResponse, 1)
+ latestTime := fri[0].Timestamp.Time()
+ for i := range fri {
+ if fri[i].Timestamp.Time().Before(latestTime) {
+ continue
+ }
+ resp[0] = fundingrate.LatestRateResponse{
+ TimeChecked: time.Now(),
+ Exchange: d.Name,
+ Asset: r.Asset,
+ Pair: r.Pair,
+ LatestRate: fundingrate.Rate{
+ Time: fri[i].Timestamp.Time(),
+ Rate: decimal.NewFromFloat(fri[i].Interest8H),
+ },
+ }
+ latestTime = fri[i].Timestamp.Time()
+ }
+ if len(resp) == 0 {
+ return nil, fmt.Errorf("%w %v %v", futures.ErrNotPerpetualFuture, r.Asset, r.Pair)
+ }
+ return resp, nil
+}
+
+// UpdateOrderExecutionLimits sets exchange execution order limits for an asset type
+func (d *Deribit) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error {
+ if !d.SupportsAsset(a) {
+ return fmt.Errorf("%s: %w - %v", d.Name, asset.ErrNotSupported, a)
+ }
+ for _, x := range baseCurrencies {
+ var instrumentsData []InstrumentData
+ var err error
+ if d.Websocket.IsConnected() {
+ instrumentsData, err = d.WSRetrieveInstrumentsData(currency.NewCode(x), d.GetAssetKind(a), false)
+ } else {
+ instrumentsData, err = d.GetInstruments(ctx, currency.NewCode(x), d.GetAssetKind(a), false)
+ }
+ if err != nil {
+ return err
+ } else if len(instrumentsData) == 0 {
+ continue
+ }
+
+ limits := make([]order.MinMaxLevel, len(instrumentsData))
+ for x := range instrumentsData {
+ var pair currency.Pair
+ pair, err = currency.NewPairFromString(instrumentsData[x].InstrumentName)
+ if err != nil {
+ return err
+ }
+ limits[x] = order.MinMaxLevel{
+ Pair: pair,
+ Asset: a,
+ PriceStepIncrementSize: instrumentsData[x].TickSize,
+ MinimumBaseAmount: instrumentsData[x].MinimumTradeAmount,
+ }
+ }
+ err = d.LoadLimits(limits)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// GetFuturesPositionSummary returns position summary details for an active position
+func (d *Deribit) GetFuturesPositionSummary(ctx context.Context, r *futures.PositionSummaryRequest) (*futures.PositionSummary, error) {
+ if r == nil {
+ return nil, fmt.Errorf("%w HistoricalRatesRequest", common.ErrNilPointer)
+ }
+ if r.Asset != asset.Futures {
+ return nil, fmt.Errorf("%w %v", futures.ErrNotPerpetualFuture, r.Asset)
+ }
+ if r.Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+ fPair, err := d.FormatExchangeCurrency(r.Pair, r.Asset)
+ if err != nil {
+ return nil, err
+ }
+ var pos []PositionData
+ if d.Websocket.IsConnected() && d.Websocket.CanUseAuthenticatedWebsocketForWrapper() {
+ pos, err = d.WSRetrievePositions(fPair.Base, d.GetAssetKind(r.Asset))
+ } else {
+ pos, err = d.GetPositions(ctx, fPair.Base, d.GetAssetKind(r.Asset))
+ }
+ if err != nil {
+ return nil, err
+ }
+ index := -1
+ for a := range pos {
+ if pos[a].InstrumentName == fPair.String() {
+ index = a
+ break
+ }
+ }
+ if index == -1 {
+ return nil, errors.New("position information for the instrument not found")
+ }
+ contracts, err := d.GetFuturesContractDetails(ctx, r.Asset)
+ if err != nil {
+ return nil, err
+ }
+ var multiplier, contractSize float64
+ var settlementType futures.ContractSettlementType
+ for i := range contracts {
+ if !contracts[i].Name.Equal(fPair) {
+ continue
+ }
+ multiplier = contracts[i].Multiplier
+ settlementType = contracts[i].SettlementType
+ break
+ }
+
+ var baseSize float64
+ if r.Asset == asset.Futures {
+ baseSize = pos[index].SizeCurrency
+ } else if r.Asset == asset.Options {
+ baseSize = pos[index].Size
+ }
+ contractSize = multiplier * baseSize
+
+ return &futures.PositionSummary{
+ Pair: r.Pair,
+ Asset: r.Asset,
+ Currency: fPair.Base,
+ NotionalSize: decimal.NewFromFloat(pos[index].MarkPrice),
+ Leverage: decimal.NewFromFloat(pos[index].Leverage),
+ InitialMarginRequirement: decimal.NewFromFloat(pos[index].InitialMargin),
+ EstimatedLiquidationPrice: decimal.NewFromFloat(pos[index].EstimatedLiquidationPrice),
+ MarkPrice: decimal.NewFromFloat(pos[index].MarkPrice),
+ CurrentSize: decimal.NewFromFloat(baseSize),
+ ContractSize: decimal.NewFromFloat(contractSize),
+ ContractMultiplier: decimal.NewFromFloat(multiplier),
+ ContractSettlementType: settlementType,
+ AverageOpenPrice: decimal.NewFromFloat(pos[index].AveragePrice),
+ UnrealisedPNL: decimal.NewFromFloat(pos[index].TotalProfitLoss - pos[index].RealizedProfitLoss),
+ RealisedPNL: decimal.NewFromFloat(pos[index].RealizedProfitLoss),
+ MaintenanceMarginFraction: decimal.NewFromFloat(pos[index].MaintenanceMargin),
+ }, nil
+}
+
+// GetOpenInterest returns the open interest rate for a given asset pair
+func (d *Deribit) GetOpenInterest(ctx context.Context, k ...key.PairAsset) ([]futures.OpenInterest, error) {
+ if len(k) == 0 {
+ return nil, fmt.Errorf("%w requires pair", common.ErrFunctionNotSupported)
+ }
+ for i := range k {
+ if k[i].Asset == asset.Spot ||
+ !d.SupportsAsset(k[i].Asset) {
+ return nil, fmt.Errorf("%w %v %v", asset.ErrNotSupported, k[i].Asset, k[i].Pair())
+ }
+ }
+ result := make([]futures.OpenInterest, 0, len(k))
+ var err error
+ var pair currency.Pair
+ for i := range k {
+ pair, err = d.FormatExchangeCurrency(k[i].Pair(), k[i].Asset)
+ if err != nil {
+ return nil, err
+ }
+ var oi []BookSummaryData
+ if d.Websocket.IsConnected() {
+ oi, err = d.WSRetrieveBookBySummary(pair.Base, d.GetAssetKind(k[i].Asset))
+ } else {
+ oi, err = d.GetBookSummaryByCurrency(ctx, pair.Base, d.GetAssetKind(k[i].Asset))
+ }
+ if err != nil {
+ return nil, err
+ }
+ for a := range oi {
+ if oi[a].InstrumentName != pair.String() {
+ continue
+ }
+ result = append(result, futures.OpenInterest{
+ Key: key.ExchangePairAsset{
+ Exchange: d.Name,
+ Base: k[i].Base,
+ Quote: k[i].Quote,
+ Asset: k[i].Asset,
+ },
+ OpenInterest: oi[a].OpenInterest,
+ })
+ break
+ }
+ }
+ if len(result) == 0 {
+ return nil, fmt.Errorf("%w, no data found for %v", currency.ErrCurrencyNotFound, k)
+ }
+ return result, nil
+}
+
+// IsPerpetualFutureCurrency ensures a given asset and currency is a perpetual future
+// differs by exchange
+func (d *Deribit) IsPerpetualFutureCurrency(assetType asset.Item, pair currency.Pair) (bool, error) {
+ if !assetType.IsFutures() {
+ return false, futures.ErrNotPerpetualFuture
+ } else if strings.EqualFold(pair.Quote.String(), "PERPETUAL") || strings.HasSuffix(pair.String(), "PERP") {
+ return true, nil
+ }
+ pair, err := d.FormatExchangeCurrency(pair, assetType)
+ if err != nil {
+ return false, err
+ }
+ var instrumentInfo *InstrumentData
+ if d.Websocket.IsConnected() {
+ instrumentInfo, err = d.WSRetrieveInstrumentData(pair.String())
+ } else {
+ instrumentInfo, err = d.GetInstrument(context.Background(), pair.String())
+ }
+ if err != nil {
+ return false, err
+ }
+ return strings.EqualFold(instrumentInfo.SettlementPeriod, "perpetual"), nil
+}
+
+// GetHistoricalFundingRates returns historical funding rates for a future
+func (d *Deribit) GetHistoricalFundingRates(ctx context.Context, r *fundingrate.HistoricalRatesRequest) (*fundingrate.HistoricalRates, error) {
+ if r == nil {
+ return nil, fmt.Errorf("%w LatestRateRequest", common.ErrNilPointer)
+ }
+ if r.Asset != asset.Futures {
+ return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, r.Asset)
+ }
+ if r.Pair.IsEmpty() {
+ return nil, currency.ErrCurrencyPairEmpty
+ }
+
+ if !r.StartDate.IsZero() && !r.EndDate.IsZero() {
+ err := common.StartEndTimeCheck(r.StartDate, r.EndDate)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if r.IncludePayments {
+ return nil, fmt.Errorf("include payments %w", common.ErrNotYetImplemented)
+ }
+ fPair, err := d.FormatExchangeCurrency(r.Pair, r.Asset)
+ if err != nil {
+ return nil, err
+ }
+ ed := r.EndDate
+
+ var fundingRates []fundingrate.Rate
+ mfr := make(map[int64]struct{})
+ for {
+ if ed.Equal(r.StartDate) || ed.Before(r.StartDate) {
+ break
+ }
+ var records []FundingRateHistory
+ if d.Websocket.IsConnected() {
+ records, err = d.WSRetrieveFundingRateHistory(fPair.String(), r.StartDate, ed)
+ } else {
+ records, err = d.GetFundingRateHistory(ctx, fPair.String(), r.StartDate, ed)
+ }
+ if err != nil {
+ return nil, err
+ }
+ if len(records) == 0 || ed.Equal(records[0].Timestamp.Time()) {
+ break
+ }
+ for i := range records {
+ rt := records[i].Timestamp.Time()
+ if rt.Before(r.StartDate) || rt.After(r.EndDate) {
+ continue
+ }
+ if _, ok := mfr[rt.UnixMilli()]; ok {
+ continue
+ }
+ fundingRates = append(fundingRates, fundingrate.Rate{
+ Rate: decimal.NewFromFloat(records[i].Interest1H),
+ Time: rt,
+ })
+ mfr[rt.UnixMilli()] = struct{}{}
+ }
+ ed = records[0].Timestamp.Time()
+ }
+ if len(fundingRates) == 0 {
+ return nil, fundingrate.ErrNoFundingRatesFound
+ }
+ sort.Slice(fundingRates, func(i, j int) bool {
+ return fundingRates[i].Time.Before(fundingRates[j].Time)
+ })
+ return &fundingrate.HistoricalRates{
+ Exchange: d.Name,
+ Asset: r.Asset,
+ Pair: r.Pair,
+ FundingRates: fundingRates,
+ StartDate: fundingRates[0].Time,
+ EndDate: r.EndDate,
+ LatestRate: fundingRates[len(fundingRates)-1],
+ PaymentCurrency: r.PaymentCurrency,
+ }, nil
+}
+
+func (d *Deribit) formatPairString(assetType asset.Item, pair currency.Pair) string {
+ switch assetType {
+ case asset.Futures:
+ return d.formatFuturesTradablePair(pair)
+ case asset.Options:
+ return d.optionPairToString(pair)
+ }
+ return pair.String()
+}
diff --git a/exchanges/deribit/ratelimit.go b/exchanges/deribit/ratelimit.go
new file mode 100644
index 00000000..563bde1a
--- /dev/null
+++ b/exchanges/deribit/ratelimit.go
@@ -0,0 +1,79 @@
+package deribit
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/thrasher-corp/gocryptotrader/exchanges/request"
+ "golang.org/x/time/rate"
+)
+
+const (
+ // request rates per interval
+ minMatchingBurst = 100
+ nonMatchingRate = 20
+ minMatchingRate = 5
+ portfoliMarginRate = 1
+
+ nonMatchingEPL request.EndpointLimit = iota
+ matchingEPL
+ portfolioMarginEPL
+ privatePortfolioMarginEPL
+)
+
+// RateLimiter holds the rate limiter to endpoints
+type RateLimiter struct {
+ NonMatchingEngine *rate.Limiter
+ MatchingEngine *rate.Limiter
+ PortfolioMargin *rate.Limiter
+ PrivatePortfolioMargin *rate.Limiter
+}
+
+// SetRateLimit returns the rate limit for the exchange
+func SetRateLimit() *RateLimiter {
+ return &RateLimiter{
+ NonMatchingEngine: request.NewRateLimit(time.Second, nonMatchingRate),
+ MatchingEngine: request.NewRateLimit(time.Second, minMatchingBurst),
+ PortfolioMargin: request.NewRateLimit(5*time.Second, portfoliMarginRate),
+ PrivatePortfolioMargin: request.NewRateLimit(5*time.Second, portfoliMarginRate),
+ }
+}
+
+// Limit executes rate limiting functionality for Binance
+func (r *RateLimiter) Limit(ctx context.Context, f request.EndpointLimit) error {
+ var limiter *rate.Limiter
+ var tokens int
+ switch f {
+ case nonMatchingEPL:
+ limiter, tokens = r.NonMatchingEngine, 1
+ case portfolioMarginEPL:
+ limiter, tokens = r.PortfolioMargin, portfoliMarginRate
+ case privatePortfolioMarginEPL:
+ limiter, tokens = r.PrivatePortfolioMargin, portfoliMarginRate
+ default:
+ limiter, tokens = r.MatchingEngine, minMatchingRate
+ }
+ var finalDelay time.Duration
+ var reserves = make([]*rate.Reservation, tokens)
+ for i := 0; i < tokens; i++ {
+ // Consume tokens 1 at a time as this avoids needing burst capacity in the limiter,
+ // which would otherwise allow the rate limit to be exceeded over short periods
+ reserves[i] = limiter.Reserve()
+ finalDelay = reserves[i].Delay()
+ }
+
+ if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(finalDelay)) {
+ // Cancel all potential reservations to free up rate limiter if deadline
+ // is exceeded.
+ for x := range reserves {
+ reserves[x].Cancel()
+ }
+ return fmt.Errorf("rate limit delay of %s will exceed deadline: %w",
+ finalDelay,
+ context.DeadlineExceeded)
+ }
+
+ time.Sleep(finalDelay)
+ return nil
+}
diff --git a/exchanges/futures/contract.go b/exchanges/futures/contract.go
index d5eec712..83ca8d02 100644
--- a/exchanges/futures/contract.go
+++ b/exchanges/futures/contract.go
@@ -79,11 +79,14 @@ const (
NineMonthly
Yearly
Unknown
+ Daily
)
// String returns the string representation of the contract type
func (c ContractType) String() string {
switch c {
+ case Daily:
+ return "day"
case Perpetual:
return "perpetual"
case LongDated:
diff --git a/exchanges/kline/kline.go b/exchanges/kline/kline.go
index 52ef66c3..2360b9bb 100644
--- a/exchanges/kline/kline.go
+++ b/exchanges/kline/kline.go
@@ -278,6 +278,8 @@ func (k *Item) FormatDates() {
// durationToWord returns english version of interval
func durationToWord(in Interval) string {
switch in {
+ case Raw:
+ return "raw"
case HundredMilliseconds:
return "hundredmillisec"
case ThousandMilliseconds:
diff --git a/exchanges/kline/kline_test.go b/exchanges/kline/kline_test.go
index a69bc0aa..e05c6325 100644
--- a/exchanges/kline/kline_test.go
+++ b/exchanges/kline/kline_test.go
@@ -154,6 +154,10 @@ func TestDurationToWord(t *testing.T) {
name string
interval Interval
}{
+ {
+ "raw",
+ Raw,
+ },
{
"hundredmillisec",
HundredMilliseconds,
diff --git a/exchanges/kline/kline_types.go b/exchanges/kline/kline_types.go
index 69636bcb..f4a1aedd 100644
--- a/exchanges/kline/kline_types.go
+++ b/exchanges/kline/kline_types.go
@@ -11,6 +11,7 @@ import (
// Consts here define basic time intervals
const (
+ Raw = Interval(-1)
HundredMilliseconds = Interval(100 * time.Millisecond)
ThousandMilliseconds = 10 * HundredMilliseconds
TenSecond = Interval(10 * time.Second)
diff --git a/exchanges/support.go b/exchanges/support.go
index 7a99883c..3b9d2ce9 100644
--- a/exchanges/support.go
+++ b/exchanges/support.go
@@ -26,6 +26,7 @@ var Exchanges = []string{
"bybit",
"coinbasepro",
"coinut",
+ "deribit",
"exmo",
"gateio",
"gemini",
diff --git a/exchanges/trade/README.md b/exchanges/trade/README.md
index 49d54e01..340315b6 100644
--- a/exchanges/trade/README.md
+++ b/exchanges/trade/README.md
@@ -72,6 +72,7 @@ _b in this context is an `IBotExchange` implemented struct_
| Bybit | Yes | Yes | Yes |
| CoinbasePro | Yes | Yes | No|
| COINUT | Yes | Yes | No |
+| Deribit | Yes | Yes | Yes |
| Exmo | Yes | NA | No |
| GateIO | Yes | Yes | No |
| Gemini | Yes | Yes | Yes |
diff --git a/testdata/configtest.json b/testdata/configtest.json
index b66ac65e..9e9ebea6 100644
--- a/testdata/configtest.json
+++ b/testdata/configtest.json
@@ -1281,6 +1281,146 @@
}
]
},
+ {
+ "name": "Deribit",
+ "enabled": true,
+ "verbose": false,
+ "httpTimeout": 15000000000,
+ "websocketResponseCheckTimeout": 30000000,
+ "websocketResponseMaxLimit": 7000000000,
+ "websocketTrafficTimeout": 30000000000,
+ "websocketOrderbookBufferLimit": 5,
+ "baseCurrencies": "BTC,ETH,SOL,USDC",
+ "currencyPairs": {
+ "assetTypes": [
+ "spot",
+ "futures",
+ "options",
+ "option_combo",
+ "future_combo"
+ ],
+ "pairs": {
+ "spot": {
+ "assetEnabled": true,
+ "enabled": "BTC_USDC,ETH_USDC,ETH_BTC",
+ "available": "BTC_USDC,ETH_USDC,ETH_BTC",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "_"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "_"
+ }
+ },
+ "futures": {
+ "assetEnabled": true,
+ "enabled": "BTC-PERPETUAL,ETH-PERPETUAL,SOL-PERPETUAL,AVAX_USDC-PERPETUAL,ETH_USDC-PERPETUAL,ADA_USDC-PERPETUAL,DOT_USDC-PERPETUAL,BTC_USDC-PERPETUAL,XRP_USDC-PERPETUAL,SOL_USDC-PERPETUAL,LTC_USDC-PERPETUAL,MATIC_USDC-PERPETUAL,UNI_USDC-PERPETUAL,LINK_USDC-PERPETUAL,ALGO_USDC-PERPETUAL",
+ "available": "BTC-21OCT22,BTC-28OCT22,BTC-25NOV22,BTC-30DEC22,BTC-31MAR23,BTC-30JUN23,BTC-29SEP23,BTC-PERPETUAL,ETH-21OCT22,ETH-28OCT22,ETH-25NOV22,ETH-30DEC22,ETH-31MAR23,ETH-30JUN23,ETH-29SEP23,ETH-PERPETUAL,SOL-21OCT22,SOL-28OCT22,SOL-25NOV22,SOL-30DEC22,SOL-PERPETUAL,AVAX_USDC-PERPETUAL,ETH_USDC-PERPETUAL,ADA_USDC-PERPETUAL,DOT_USDC-PERPETUAL,BTC_USDC-PERPETUAL,XRP_USDC-PERPETUAL,SOL_USDC-PERPETUAL,LTC_USDC-PERPETUAL,MATIC_USDC-PERPETUAL,UNI_USDC-PERPETUAL,LINK_USDC-PERPETUAL,ALGO_USDC-PERPETUAL,NEAR_USDC-PERPETUAL,TRX_USDC-PERPETUAL,DOGE_USDC-PERPETUAL,BCH_USDC-PERPETUAL",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ },
+ "options": {
+ "assetEnabled": true,
+ "enabled": "SOL-21OCT22-20-C,SOL-21OCT22-20-P,SOL-21OCT22-24-C,SOL-21OCT22-24-P,SOL-21OCT22-25-C,SOL-21OCT22-25-P",
+ "available": "SOL-21OCT22-20-C,SOL-21OCT22-20-P,SOL-21OCT22-24-C,SOL-21OCT22-24-P,SOL-21OCT22-25-C,SOL-21OCT22-25-P,SOL-21OCT22-26-C,SOL-21OCT22-26-P,SOL-21OCT22-27-C,SOL-21OCT22-27-P,SOL-21OCT22-28-C,SOL-21OCT22-28-P,SOL-21OCT22-29-C,SOL-21OCT22-29-P,SOL-21OCT22-30-C,SOL-21OCT22-30-P,SOL-21OCT22-31-C,SOL-21OCT22-31-P,SOL-21OCT22-32-C,SOL-21OCT22-32-P,SOL-21OCT22-33-C,SOL-21OCT22-33-P,SOL-21OCT22-34-C,SOL-21OCT22-34-P,SOL-21OCT22-35-C,SOL-21OCT22-35-P,SOL-21OCT22-36-C,SOL-21OCT22-36-P,SOL-21OCT22-37-C,SOL-21OCT22-37-P,SOL-21OCT22-38-C,SOL-21OCT22-38-P,SOL-21OCT22-39-C,SOL-21OCT22-39-P,SOL-21OCT22-40-C,SOL-21OCT22-40-P,SOL-21OCT22-45-C,SOL-21OCT22-45-P,SOL-22OCT22-20-C,SOL-22OCT22-20-P,SOL-22OCT22-22-C,SOL-22OCT22-22-P,SOL-22OCT22-24-C,SOL-22OCT22-24-P,SOL-22OCT22-26-C,SOL-22OCT22-26-P,SOL-22OCT22-27-C,SOL-22OCT22-27-P,SOL-22OCT22-28-C,SOL-22OCT22-28-P,SOL-22OCT22-29-C,SOL-22OCT22-29-P,SOL-22OCT22-30-C,SOL-22OCT22-30-P,SOL-22OCT22-31-C,SOL-22OCT22-31-P,SOL-22OCT22-32-C,SOL-22OCT22-32-P,SOL-22OCT22-33-C,SOL-22OCT22-33-P,SOL-22OCT22-34-C,SOL-22OCT22-34-P,SOL-22OCT22-36-C,SOL-22OCT22-36-P,SOL-28OCT22-10-C,SOL-28OCT22-10-P,SOL-28OCT22-20-C,SOL-28OCT22-20-P,SOL-28OCT22-24-C,SOL-28OCT22-24-P,SOL-28OCT22-25-C,SOL-28OCT22-25-P,SOL-28OCT22-26-C,SOL-28OCT22-26-P,SOL-28OCT22-27-C,SOL-28OCT22-27-P,SOL-28OCT22-28-C,SOL-28OCT22-28-P,SOL-28OCT22-29-C,SOL-28OCT22-29-P,SOL-28OCT22-30-C,SOL-28OCT22-30-P,SOL-28OCT22-31-C,SOL-28OCT22-31-P,SOL-28OCT22-32-C,SOL-28OCT22-32-P,SOL-28OCT22-33-C,SOL-28OCT22-33-P,SOL-28OCT22-34-C,SOL-28OCT22-34-P,SOL-28OCT22-35-C,SOL-28OCT22-35-P,SOL-28OCT22-36-C,SOL-28OCT22-36-P,SOL-28OCT22-38-C,SOL-28OCT22-38-P,SOL-28OCT22-40-C,SOL-28OCT22-40-P,SOL-28OCT22-42-C,SOL-28OCT22-42-P,SOL-28OCT22-44-C,SOL-28OCT22-44-P,SOL-28OCT22-45-C,SOL-28OCT22-45-P,SOL-28OCT22-50-C,SOL-28OCT22-50-P,SOL-28OCT22-60-C,SOL-28OCT22-60-P,SOL-4NOV22-15-C,SOL-4NOV22-15-P,SOL-4NOV22-20-C,SOL-4NOV22-20-P,SOL-4NOV22-22-C,SOL-4NOV22-22-P,SOL-4NOV22-23-C,SOL-4NOV22-23-P,SOL-4NOV22-24-C,SOL-4NOV22-24-P,SOL-4NOV22-25-C,SOL-4NOV22-25-P,SOL-4NOV22-26-C,SOL-4NOV22-26-P,SOL-4NOV22-27-C,SOL-4NOV22-27-P,SOL-4NOV22-28-C,SOL-4NOV22-28-P,SOL-4NOV22-29-C,SOL-4NOV22-29-P,SOL-4NOV22-30-C,SOL-4NOV22-30-P,SOL-4NOV22-31-C,SOL-4NOV22-31-P,SOL-4NOV22-32-C,SOL-4NOV22-32-P,SOL-4NOV22-33-C,SOL-4NOV22-33-P,SOL-4NOV22-34-C,SOL-4NOV22-34-P,SOL-4NOV22-35-C,SOL-4NOV22-35-P,SOL-4NOV22-40-C,SOL-4NOV22-40-P,SOL-25NOV22-15-C,SOL-25NOV22-15-P,SOL-25NOV22-20-C,SOL-25NOV22-20-P,SOL-25NOV22-22-C,SOL-25NOV22-22-P,SOL-25NOV22-24-C,SOL-25NOV22-24-P,SOL-25NOV22-25-C,SOL-25NOV22-25-P,SOL-25NOV22-26-C,SOL-25NOV22-26-P,SOL-25NOV22-28-C,SOL-25NOV22-28-P,SOL-25NOV22-30-C,SOL-25NOV22-30-P,SOL-25NOV22-32-C,SOL-25NOV22-32-P,SOL-25NOV22-34-C,SOL-25NOV22-34-P,SOL-25NOV22-35-C,SOL-25NOV22-35-P,SOL-25NOV22-36-C,SOL-25NOV22-36-P,SOL-25NOV22-38-C,SOL-25NOV22-38-P,SOL-25NOV22-40-C,SOL-25NOV22-40-P,SOL-25NOV22-45-C,SOL-25NOV22-45-P,SOL-25NOV22-50-C,SOL-25NOV22-50-P,SOL-30DEC22-10-C,SOL-30DEC22-10-P,SOL-30DEC22-15-C,SOL-30DEC22-15-P,SOL-30DEC22-20-C,SOL-30DEC22-20-P,SOL-30DEC22-24-C,SOL-30DEC22-24-P,SOL-30DEC22-25-C,SOL-30DEC22-25-P,SOL-30DEC22-26-C,SOL-30DEC22-26-P,SOL-30DEC22-28-C,SOL-30DEC22-28-P,SOL-30DEC22-30-C,SOL-30DEC22-30-P,SOL-30DEC22-32-C,SOL-30DEC22-32-P,SOL-30DEC22-34-C,SOL-30DEC22-34-P,SOL-30DEC22-35-C,SOL-30DEC22-35-P,SOL-30DEC22-36-C,SOL-30DEC22-36-P,SOL-30DEC22-40-C,SOL-30DEC22-40-P,SOL-30DEC22-45-C,SOL-30DEC22-45-P,SOL-30DEC22-50-C,SOL-30DEC22-50-P,SOL-30DEC22-60-C,SOL-30DEC22-60-P,SOL-30DEC22-70-C,SOL-30DEC22-70-P,ETH-21OCT22-600-C,ETH-21OCT22-600-P,ETH-21OCT22-800-C,ETH-21OCT22-800-P,ETH-21OCT22-900-C,ETH-21OCT22-900-P,ETH-21OCT22-1000-C,ETH-21OCT22-1000-P,ETH-21OCT22-1100-C,ETH-21OCT22-1100-P,ETH-21OCT22-1150-C,ETH-21OCT22-1150-P,ETH-21OCT22-1200-C,ETH-21OCT22-1200-P,ETH-21OCT22-1225-C,ETH-21OCT22-1225-P,ETH-21OCT22-1250-C,ETH-21OCT22-1250-P,ETH-21OCT22-1275-C,ETH-21OCT22-1275-P,ETH-21OCT22-1300-C,ETH-21OCT22-1300-P,ETH-21OCT22-1325-C,ETH-21OCT22-1325-P,ETH-21OCT22-1350-C,ETH-21OCT22-1350-P,ETH-21OCT22-1400-C,ETH-21OCT22-1400-P,ETH-21OCT22-1450-C,ETH-21OCT22-1450-P,ETH-21OCT22-1500-C,ETH-21OCT22-1500-P,ETH-21OCT22-1600-C,ETH-21OCT22-1600-P,ETH-21OCT22-1700-C,ETH-21OCT22-1700-P,ETH-21OCT22-1800-C,ETH-21OCT22-1800-P,ETH-21OCT22-2000-C,ETH-21OCT22-2000-P,ETH-21OCT22-2200-C,ETH-21OCT22-2200-P,ETH-21OCT22-2400-C,ETH-21OCT22-2400-P,ETH-22OCT22-900-C,ETH-22OCT22-900-P,ETH-22OCT22-1000-C,ETH-22OCT22-1000-P,ETH-22OCT22-1100-C,ETH-22OCT22-1100-P,ETH-22OCT22-1150-C,ETH-22OCT22-1150-P,ETH-22OCT22-1200-C,ETH-22OCT22-1200-P,ETH-22OCT22-1225-C,ETH-22OCT22-1225-P,ETH-22OCT22-1250-C,ETH-22OCT22-1250-P,ETH-22OCT22-1275-C,ETH-22OCT22-1275-P,ETH-22OCT22-1300-C,ETH-22OCT22-1300-P,ETH-22OCT22-1325-C,ETH-22OCT22-1325-P,ETH-22OCT22-1350-C,ETH-22OCT22-1350-P,ETH-22OCT22-1375-C,ETH-22OCT22-1375-P,ETH-22OCT22-1400-C,ETH-22OCT22-1400-P,ETH-22OCT22-1450-C,ETH-22OCT22-1450-P,ETH-22OCT22-1500-C,ETH-22OCT22-1500-P,ETH-28OCT22-600-C,ETH-28OCT22-600-P,ETH-28OCT22-800-C,ETH-28OCT22-800-P,ETH-28OCT22-900-C,ETH-28OCT22-900-P,ETH-28OCT22-1000-C,ETH-28OCT22-1000-P,ETH-28OCT22-1100-C,ETH-28OCT22-1100-P,ETH-28OCT22-1150-C,ETH-28OCT22-1150-P,ETH-28OCT22-1200-C,ETH-28OCT22-1200-P,ETH-28OCT22-1250-C,ETH-28OCT22-1250-P,ETH-28OCT22-1300-C,ETH-28OCT22-1300-P,ETH-28OCT22-1350-C,ETH-28OCT22-1350-P,ETH-28OCT22-1400-C,ETH-28OCT22-1400-P,ETH-28OCT22-1450-C,ETH-28OCT22-1450-P,ETH-28OCT22-1500-C,ETH-28OCT22-1500-P,ETH-28OCT22-1550-C,ETH-28OCT22-1550-P,ETH-28OCT22-1600-C,ETH-28OCT22-1600-P,ETH-28OCT22-1650-C,ETH-28OCT22-1650-P,ETH-28OCT22-1700-C,ETH-28OCT22-1700-P,ETH-28OCT22-1750-C,ETH-28OCT22-1750-P,ETH-28OCT22-1800-C,ETH-28OCT22-1800-P,ETH-28OCT22-1850-C,ETH-28OCT22-1850-P,ETH-28OCT22-1900-C,ETH-28OCT22-1900-P,ETH-28OCT22-1950-C,ETH-28OCT22-1950-P,ETH-28OCT22-2000-C,ETH-28OCT22-2000-P,ETH-28OCT22-2050-C,ETH-28OCT22-2050-P,ETH-28OCT22-2100-C,ETH-28OCT22-2100-P,ETH-28OCT22-2200-C,ETH-28OCT22-2200-P,ETH-28OCT22-2300-C,ETH-28OCT22-2300-P,ETH-28OCT22-2400-C,ETH-28OCT22-2400-P,ETH-28OCT22-2500-C,ETH-28OCT22-2500-P,ETH-28OCT22-2600-C,ETH-28OCT22-2600-P,ETH-28OCT22-2700-C,ETH-28OCT22-2700-P,ETH-28OCT22-2800-C,ETH-28OCT22-2800-P,ETH-28OCT22-2900-C,ETH-28OCT22-2900-P,ETH-28OCT22-3000-C,ETH-28OCT22-3000-P,ETH-28OCT22-3100-C,ETH-28OCT22-3100-P,ETH-28OCT22-3200-C,ETH-28OCT22-3200-P,ETH-28OCT22-3400-C,ETH-28OCT22-3400-P,ETH-28OCT22-3600-C,ETH-28OCT22-3600-P,ETH-28OCT22-3800-C,ETH-28OCT22-3800-P,ETH-28OCT22-4000-C,ETH-28OCT22-4000-P,ETH-28OCT22-4200-C,ETH-28OCT22-4200-P,ETH-4NOV22-600-C,ETH-4NOV22-600-P,ETH-4NOV22-800-C,ETH-4NOV22-800-P,ETH-4NOV22-900-C,ETH-4NOV22-900-P,ETH-4NOV22-1000-C,ETH-4NOV22-1000-P,ETH-4NOV22-1050-C,ETH-4NOV22-1050-P,ETH-4NOV22-1100-C,ETH-4NOV22-1100-P,ETH-4NOV22-1150-C,ETH-4NOV22-1150-P,ETH-4NOV22-1200-C,ETH-4NOV22-1200-P,ETH-4NOV22-1250-C,ETH-4NOV22-1250-P,ETH-4NOV22-1300-C,ETH-4NOV22-1300-P,ETH-4NOV22-1350-C,ETH-4NOV22-1350-P,ETH-4NOV22-1400-C,ETH-4NOV22-1400-P,ETH-4NOV22-1450-C,ETH-4NOV22-1450-P,ETH-4NOV22-1500-C,ETH-4NOV22-1500-P,ETH-4NOV22-1600-C,ETH-4NOV22-1600-P,ETH-4NOV22-1800-C,ETH-4NOV22-1800-P,ETH-4NOV22-2000-C,ETH-4NOV22-2000-P,ETH-4NOV22-2200-C,ETH-4NOV22-2200-P,ETH-4NOV22-2400-C,ETH-4NOV22-2400-P,ETH-11NOV22-600-C,ETH-11NOV22-600-P,ETH-11NOV22-800-C,ETH-11NOV22-800-P,ETH-11NOV22-900-C,ETH-11NOV22-900-P,ETH-11NOV22-1000-C,ETH-11NOV22-1000-P,ETH-11NOV22-1100-C,ETH-11NOV22-1100-P,ETH-11NOV22-1150-C,ETH-11NOV22-1150-P,ETH-11NOV22-1200-C,ETH-11NOV22-1200-P,ETH-11NOV22-1250-C,ETH-11NOV22-1250-P,ETH-11NOV22-1300-C,ETH-11NOV22-1300-P,ETH-11NOV22-1350-C,ETH-11NOV22-1350-P,ETH-11NOV22-1400-C,ETH-11NOV22-1400-P,ETH-11NOV22-1500-C,ETH-11NOV22-1500-P,ETH-11NOV22-1600-C,ETH-11NOV22-1600-P,ETH-11NOV22-1800-C,ETH-11NOV22-1800-P,ETH-11NOV22-2000-C,ETH-11NOV22-2000-P,ETH-11NOV22-2200-C,ETH-11NOV22-2200-P,ETH-11NOV22-2400-C,ETH-11NOV22-2400-P,ETH-25NOV22-600-C,ETH-25NOV22-600-P,ETH-25NOV22-700-C,ETH-25NOV22-700-P,ETH-25NOV22-800-C,ETH-25NOV22-800-P,ETH-25NOV22-900-C,ETH-25NOV22-900-P,ETH-25NOV22-1000-C,ETH-25NOV22-1000-P,ETH-25NOV22-1050-C,ETH-25NOV22-1050-P,ETH-25NOV22-1100-C,ETH-25NOV22-1100-P,ETH-25NOV22-1150-C,ETH-25NOV22-1150-P,ETH-25NOV22-1200-C,ETH-25NOV22-1200-P,ETH-25NOV22-1250-C,ETH-25NOV22-1250-P,ETH-25NOV22-1300-C,ETH-25NOV22-1300-P,ETH-25NOV22-1350-C,ETH-25NOV22-1350-P,ETH-25NOV22-1400-C,ETH-25NOV22-1400-P,ETH-25NOV22-1450-C,ETH-25NOV22-1450-P,ETH-25NOV22-1500-C,ETH-25NOV22-1500-P,ETH-25NOV22-1600-C,ETH-25NOV22-1600-P,ETH-25NOV22-1700-C,ETH-25NOV22-1700-P,ETH-25NOV22-1800-C,ETH-25NOV22-1800-P,ETH-25NOV22-1900-C,ETH-25NOV22-1900-P,ETH-25NOV22-2000-C,ETH-25NOV22-2000-P,ETH-25NOV22-2100-C,ETH-25NOV22-2100-P,ETH-25NOV22-2200-C,ETH-25NOV22-2200-P,ETH-25NOV22-2300-C,ETH-25NOV22-2300-P,ETH-25NOV22-2400-C,ETH-25NOV22-2400-P,ETH-25NOV22-2500-C,ETH-25NOV22-2500-P,ETH-25NOV22-2600-C,ETH-25NOV22-2600-P,ETH-25NOV22-2700-C,ETH-25NOV22-2700-P,ETH-25NOV22-2800-C,ETH-25NOV22-2800-P,ETH-25NOV22-3000-C,ETH-25NOV22-3000-P,ETH-25NOV22-3200-C,ETH-25NOV22-3200-P,ETH-25NOV22-3400-C,ETH-25NOV22-3400-P,ETH-25NOV22-3600-C,ETH-25NOV22-3600-P,ETH-25NOV22-3800-C,ETH-25NOV22-3800-P,ETH-30DEC22-300-C,ETH-30DEC22-300-P,ETH-30DEC22-400-C,ETH-30DEC22-400-P,BTC-21OCT22-10000-C,BTC-21OCT22-10000-P,BTC-21OCT22-11000-C,BTC-21OCT22-11000-P,BTC-21OCT22-12000-C,BTC-21OCT22-12000-P,BTC-21OCT22-13000-C,BTC-21OCT22-13000-P,BTC-21OCT22-14000-C,BTC-21OCT22-14000-P,BTC-21OCT22-15000-C,BTC-21OCT22-15000-P,BTC-21OCT22-16000-C,BTC-21OCT22-16000-P,BTC-21OCT22-17000-C,BTC-21OCT22-17000-P,BTC-21OCT22-17500-C,BTC-21OCT22-17500-P,BTC-21OCT22-18000-C,BTC-21OCT22-18000-P,BTC-21OCT22-18500-C,BTC-21OCT22-18500-P,BTC-21OCT22-18750-C,BTC-21OCT22-18750-P,BTC-21OCT22-19000-C,BTC-21OCT22-19000-P,BTC-21OCT22-19250-C,BTC-21OCT22-19250-P,BTC-21OCT22-19500-C,BTC-21OCT22-19500-P,BTC-21OCT22-20000-C,BTC-21OCT22-20000-P,BTC-21OCT22-20500-C,BTC-21OCT22-20500-P,BTC-21OCT22-21000-C,BTC-21OCT22-21000-P,BTC-21OCT22-21500-C,BTC-21OCT22-21500-P,BTC-21OCT22-22000-C,BTC-21OCT22-22000-P,BTC-21OCT22-23000-C,BTC-21OCT22-23000-P,BTC-21OCT22-24000-C,BTC-21OCT22-24000-P",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ },
+ "option_combo": {
+ "assetEnabled": true,
+ "enabled": "BTC-STRG-21OCT22-19000_21000,BTC-CBUT-21OCT22-18500_19000_19500,BTC-CBUT-21OCT22-18000_19500_21000",
+ "available": "BTC-STRG-21OCT22-19000_21000,BTC-CBUT-21OCT22-18500_19000_19500,BTC-CBUT-21OCT22-18000_19500_21000,BTC-CBUT-21OCT22-19000_20000_21000,BTC-STRD-21OCT22-20000,BTC-STRD-21OCT22-19500,BTC-PS-21OCT22-19500_19000,BTC-CS-28OCT22-24000_32000,BTC-PCAL-25NOV22_28OCT22-12000,BTC-PS-28OCT22-19000_18000,BTC-ICOND-28OCT22-15000_16000_22000_23000,BTC-STRG-28OCT22-17500_20500,BTC-STRG-28OCT22-18000_20000,BTC-STRG-28OCT22-18500_20000,BTC-PS-28OCT22-20000_16000,BTC-PS-28OCT22-18000_17000,BTC-STRG-28OCT22-19000_22000,BTC-PBUT-4NOV22-17500_18500_19500,BTC-PSR13-11NOV22-14000_11000,BTC-PBUT-25NOV22-24000_30000_35000,BTC-STRG-25NOV22-14000_26000,BTC-CS-25NOV22-22000_23000,BTC-PSR12-25NOV22-18000_16000,BTC-STRG-25NOV22-19000_20000,BTC-STRG-25NOV22-16000_22000,BTC-CS-25NOV22-24000_26000,BTC-RR-25NOV22-18000_20000,BTC-PDIAG-30DEC22_25NOV22-16000_18000,BTC-PSR12-25NOV22-18000_17000,BTC-CS-25NOV22-20000_23000,BTC-PCAL-31MAR23_30DEC22-20000,BTC-CCAL-31MAR23_30DEC22-18000,BTC-CCAL-31MAR23_30DEC22-19000,BTC-CBUT-30DEC22-20000_25000_30000,BTC-STRD-30DEC22-20000,BTC-STRG-30DEC22-15000_28000,BTC-RR-30DEC22-18000_22000,BTC-PCAL-31MAR23_30DEC22-19000,BTC-PBUT111-30DEC22-12000_17000_19000,BTC-PS-30DEC22-12000_10000,BTC-PSR12-30DEC22-17000_16000,BTC-CBUT-31MAR23-22000_28000_34000,BTC-STRG-31MAR23-13000_38000,BTC-STRD-31MAR23-22000,BTC-PSR13-31MAR23-15000_14000,BTC-PSR13-31MAR23-25000_18000,BTC-PSR13-30JUN23-18000_15000,BTC-CBUT-29SEP23-20000_40000_60000,BTC-STRD-29SEP23-24000,ETH-PSR12-21OCT22-1250_1150,ETH-PSR12-21OCT22-1300_1200,ETH-STRG-21OCT22-1150_1450,ETH-PSR12-21OCT22-1200_1100,ETH-STRD-21OCT22-1500,ETH-STRG-21OCT22-1200_1400,ETH-PS-28OCT22-1500_1100,ETH-CS-28OCT22-1700_1950,ETH-PSR13-28OCT22-1200_1000,ETH-STRD-28OCT22-1350,ETH-CS-28OCT22-1400_1500,ETH-CS-28OCT22-1450_1550,ETH-PS-28OCT22-1000_900,ETH-CS-28OCT22-1400_1600,ETH-PCOND-28OCT22-900_1000_1400_1450,ETH-PSR12-28OCT22-1250_1100,ETH-IBUT-28OCT22-1250_1300_1350,ETH-PS-25NOV22-1400_1000,ETH-CSR13-25NOV22-1400_1800,ETH-CS-25NOV22-1500_2000,ETH-CS-25NOV22-1500_1700,ETH-CS-25NOV22-1500_1800,ETH-CBUT-25NOV22-1200_1300_1400,ETH-CBUT-25NOV22-1100_1300_1500,ETH-PSR12-25NOV22-1250_1100,ETH-CS-25NOV22-1300_1400,ETH-PBUT-25NOV22-1200_1300_1400,ETH-PSR12-25NOV22-1250_1150,ETH-CSR12-25NOV22-1300_1400,ETH-PSR12-30DEC22-1400_1200,ETH-STRD-30DEC22-1600,ETH-PCAL-31MAR23_30DEC22-2500,ETH-CS-30DEC22-1500_1900,ETH-CS-30DEC22-1700_1800,ETH-CS-30DEC22-1600_1800,ETH-PS-30DEC22-1300_1000,ETH-PDIAG-31MAR23_30DEC22-2500_3000,ETH-STRD-30DEC22-1400,ETH-STRD-30DEC22-1300,ETH-CS-30DEC22-1600_1900,ETH-CCAL-31MAR23_30DEC22-1300,ETH-CCAL-31MAR23_30DEC22-1200,ETH-CCAL-31MAR23_30DEC22-1400,ETH-PS-30DEC22-1500_1300,ETH-CBUT-30DEC22-1000_1300_2000,ETH-REV-31MAR23-1000,ETH-REV-31MAR23-1300,ETH-CS-31MAR23-1700_2000,ETH-STRG-31MAR23-800_3000,ETH-PS-31MAR23-1300_1000,SOL-PLAD-21OCT22-31_30_29,SOL-PS-28OCT22-32_28,SOL-RR-28OCT22-32_38,SOL-STRG-28OCT22-20_50,SOL-ICOND-28OCT22-20_25_45_50,SOL-PCAL-25NOV22_28OCT22-30,SOL-PSR12-28OCT22-30_29,SOL-CS-25NOV22-40_45",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ },
+ "future_combo": {
+ "assetEnabled": true,
+ "enabled": "SOL-FS-30DEC22_28OCT22,ETH-FS-28OCT22_PERP,ETH-FS-30DEC22_28OCT22,ETH-FS-30DEC22_PERP,ETH-FS-31MAR23_30DEC22",
+ "available": "SOL-FS-30DEC22_28OCT22,ETH-FS-28OCT22_PERP,ETH-FS-30DEC22_28OCT22,ETH-FS-30DEC22_PERP,ETH-FS-31MAR23_30DEC22,ETH-FS-29SEP23_30DEC22,ETH-FS-30JUN23_30DEC22,ETH-FS-31MAR23_PERP,ETH-FS-30JUN23_31MAR23,ETH-FS-30JUN23_PERP,ETH-FS-29SEP23_30JUN23,ETH-FS-29SEP23_PERP,BTC-FS-31MAR23_21OCT22,BTC-FS-21OCT22_PERP,BTC-FS-28OCT22_PERP,BTC-FS-30JUN23_28OCT22,BTC-FS-31MAR23_28OCT22,BTC-FS-29SEP23_28OCT22,BTC-FS-25NOV22_28OCT22,BTC-FS-29SEP23_25NOV22,BTC-FS-30DEC22_PERP,BTC-FS-30JUN23_30DEC22,BTC-FS-31MAR23_30DEC22,BTC-FS-29SEP23_30DEC22,BTC-FS-31MAR23_PERP,BTC-FS-30JUN23_31MAR23,BTC-FS-29SEP23_31MAR23,BTC-FS-30JUN23_PERP,BTC-FS-29SEP23_30JUN23,BTC-FS-29SEP23_PERP",
+ "requestFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ },
+ "configFormat": {
+ "uppercase": true,
+ "delimiter": "-"
+ }
+ }
+ }
+ },
+ "api": {
+ "authenticatedSupport": true,
+ "authenticatedWebsocketApiSupport": true,
+ "endpoints": {
+ "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
+ "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API",
+ "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
+ },
+ "credentials": {
+ "key": "Key",
+ "secret": "Secret"
+ },
+ "credentialsValidator": {
+ "requiresKey": true,
+ "requiresSecret": true
+ },
+ "urlEndpoints": {
+ "RestFuturesURL": "https://www.deribit.com",
+ "RestSpotURL": "https://test.deribit.com"
+ }
+ },
+ "features": {
+ "supports": {
+ "restAPI": true,
+ "restCapabilities": {
+ "autoPairUpdates": true
+ },
+ "websocketAPI": true,
+ "websocketCapabilities": {}
+ },
+ "enabled": {
+ "autoPairUpdates": true,
+ "websocketAPI": true,
+ "saveTradeData": false,
+ "tradeFeed": true,
+ "fillsFeed": false
+ }
+ },
+ "bankAccounts": [
+ {
+ "enabled": false,
+ "bankName": "",
+ "bankAddress": "",
+ "bankPostalCode": "",
+ "bankPostalCity": "",
+ "bankCountry": "",
+ "accountName": "",
+ "accountNumber": "",
+ "swiftCode": "",
+ "iban": "",
+ "supportedCurrencies": ""
+ }
+ ]
+ },
{
"name": "EXMO",
"enabled": true,
diff --git a/testdata/exchangelist.csv b/testdata/exchangelist.csv
index bc6c6695..774a8815 100644
--- a/testdata/exchangelist.csv
+++ b/testdata/exchangelist.csv
@@ -10,6 +10,7 @@ btse,
bybit,
coinbasepro,
coinut,
+deribit,
exmo,
gateio,
gemini,