Files
gocryptotrader/exchanges/deribit/deribit_ws_endpoints.go
Copilot a9f23018e3 Deribit: Remove deprecated RFQ endpoints (#2078)
* Initial plan

* Remove deprecated Deribit RFQ endpoints

Co-authored-by: thrasher- <4685270+thrasher-@users.noreply.github.com>

* Remove extra newline before TestGetTradeVolumes

Co-authored-by: thrasher- <4685270+thrasher-@users.noreply.github.com>

* Remove extra newline before TestSetMMPConfig

Co-authored-by: thrasher- <4685270+thrasher-@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thrasher- <4685270+thrasher-@users.noreply.github.com>
2025-10-14 09:08:45 +11:00

2343 lines
88 KiB
Go

package deribit
import (
"context"
"errors"
"fmt"
"net/url"
"strings"
"time"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"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 (e *Exchange) WSRetrieveBookBySummary(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getBookByCurrency, input, &resp, false)
}
// WSRetrieveBookSummaryByInstrument retrieves book summary data for instrument requested through the websocket connection.
func (e *Exchange) WSRetrieveBookSummaryByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getBookByInstrument, input, &resp, false)
}
// WSRetrieveContractSize retrieves contract size for instrument requested through the websocket connection.
func (e *Exchange) WSRetrieveContractSize(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getContractSize, input, &resp, false)
}
// WSRetrieveCurrencies retrieves all cryptocurrencies supported by the API through the websocket connection.
func (e *Exchange) WSRetrieveCurrencies(ctx context.Context) ([]CurrencyData, error) {
var resp []CurrencyData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getCurrencies, nil, &resp, false)
}
// WSRetrieveDeliveryPrices retrieves delivery prices using index name through the websocket connection.
func (e *Exchange) WSRetrieveDeliveryPrices(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrieveFundingChartData(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getFundingChartData, input, &resp, false)
}
// WSRetrieveFundingRateHistory retrieves hourly historical interest rate for requested PERPETUAL instrument through the websocket connection.
func (e *Exchange) WSRetrieveFundingRateHistory(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getFundingRateHistory, input, &resp, false)
}
// WSRetrieveFundingRateValue retrieves funding rate value data through the websocket connection.
func (e *Exchange) WSRetrieveFundingRateValue(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getFundingRateValue, input, &resp, false)
}
// WSRetrieveHistoricalVolatility retrieves historical volatility data
func (e *Exchange) WSRetrieveHistoricalVolatility(ctx context.Context, ccy currency.Code) ([]HistoricalVolatilityData, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
input := &struct {
Currency currency.Code `json:"currency"`
}{
Currency: ccy,
}
var data []HistoricalVolatilityData
return data, e.SendWSRequest(ctx, nonMatchingEPL, getHistoricalVolatility, input, &data, false)
}
// WSRetrieveCurrencyIndexPrice the current index price for the instruments, for the selected currency through the websocket connection.
func (e *Exchange) WSRetrieveCurrencyIndexPrice(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getCurrencyIndexPrice, input, &resp, false)
}
// WSRetrieveIndexPrice retrieves price data for the requested index through the websocket connection.
func (e *Exchange) WSRetrieveIndexPrice(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getIndexPrice, input, &resp, false)
}
// WSRetrieveIndexPriceNames names of indexes through the websocket connection.
func (e *Exchange) WSRetrieveIndexPriceNames(ctx context.Context) ([]string, error) {
var resp []string
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getIndexPriceNames, nil, &resp, false)
}
// WSRetrieveInstrumentData retrieves data for a requested instrument through the websocket connection.
func (e *Exchange) WSRetrieveInstrumentData(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getInstrument, input, &resp, false)
}
// WSRetrieveInstrumentsData gets data for all available instruments
func (e *Exchange) WSRetrieveInstrumentsData(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getInstruments, input, &resp, false)
}
// WSRetrieveLastSettlementsByCurrency retrieves last settlement data by currency through the websocket connection.
func (e *Exchange) WSRetrieveLastSettlementsByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastSettlementsByCurrency, input, &resp, false)
}
// WSRetrieveLastSettlementsByInstrument retrieves last settlement data for requested instrument through the websocket connection.
func (e *Exchange) WSRetrieveLastSettlementsByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastSettlementsByInstrument, input, &resp, false)
}
// WSRetrieveLastTradesByCurrency retrieves last trades for requested currency through the websocket connection.
func (e *Exchange) WSRetrieveLastTradesByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastTradesByCurrency, input, &resp, false)
}
// WSRetrieveLastTradesByCurrencyAndTime retrieves last trades for requested currency and time intervals through the websocket connection.
func (e *Exchange) WSRetrieveLastTradesByCurrencyAndTime(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastTradesByCurrencyAndTime, input, &resp, false)
}
// WSRetrieveLastTradesByInstrument retrieves last trades for requested instrument requested through the websocket connection.
func (e *Exchange) WSRetrieveLastTradesByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastTradesByInstrument, input, &resp, false)
}
// WSRetrieveLastTradesByInstrumentAndTime retrieves last trades for requested instrument requested and time intervals through the websocket connection.
func (e *Exchange) WSRetrieveLastTradesByInstrumentAndTime(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastTradesByInstrumentAndTime, input, &resp, false)
}
// WSRetrieveMarkPriceHistory retrieves data for mark price history through the websocket connection.
func (e *Exchange) WSRetrieveMarkPriceHistory(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getMarkPriceHistory, input, &resp, false)
}
// WSRetrieveOrderbookData retrieves data orderbook of requested instrument through the web-socket connection.
func (e *Exchange) WSRetrieveOrderbookData(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOrderbook, input, &resp, false)
}
// WSRetrieveOrderbookByInstrumentID retrieves orderbook by instrument ID through websocket connection.
func (e *Exchange) WSRetrieveOrderbookByInstrumentID(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WsRetrieveSupportedIndexNames(ctx context.Context, priceIndexType string) ([]string, error) {
input := &struct {
PriceIndexType string `json:"type,omitempty"`
}{
PriceIndexType: priceIndexType,
}
var resp []string
return resp, e.SendWSRequest(ctx, nonMatchingEPL, "public/get_supported_index_names", input, &resp, false)
}
// WSRetrieveTradeVolumes retrieves trade volumes' data of all instruments through the websocket connection.
func (e *Exchange) WSRetrieveTradeVolumes(ctx context.Context, extended bool) ([]TradeVolumesData, error) {
input := &struct {
Extended bool `json:"extended,omitempty"`
}{
Extended: extended,
}
var resp []TradeVolumesData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getTradeVolumes, input, &resp, false)
}
// WSRetrievesTradingViewChartData retrieves volatility index data for the requested instrument through the websocket connection.
func (e *Exchange) WSRetrievesTradingViewChartData(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getTradingViewChartData, input, &resp, false)
}
// WSRetrieveVolatilityIndexData retrieves volatility index data for the requested currency through the websocket connection.
func (e *Exchange) WSRetrieveVolatilityIndexData(ctx context.Context, 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 = e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrievePublicTicker(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getTicker, input, &resp, false)
}
// WSRetrieveAccountSummary retrieves account summary data for the requested instrument through the websocket connection.
func (e *Exchange) WSRetrieveAccountSummary(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getAccountSummary, input, &resp, true)
}
// WSCancelWithdrawal cancels withdrawal request for a given currency by its id through the websocket connection.
func (e *Exchange) WSCancelWithdrawal(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)
}
input := &struct {
Currency currency.Code `json:"currency"`
ID int64 `json:"id"`
}{
Currency: ccy,
ID: id,
}
var resp *CancelWithdrawalData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, cancelWithdrawal, input, &resp, true)
}
// WSCancelTransferByID cancels transfer by ID through the websocket connection.
func (e *Exchange) WSCancelTransferByID(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)
}
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, e.SendWSRequest(ctx, nonMatchingEPL, cancelTransferByID, input, &resp, true)
}
// WSCreateDepositAddress creates a deposit address for the currency requested through the websocket connection.
func (e *Exchange) WSCreateDepositAddress(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, createDepositAddress, input, &resp, true)
}
// WSRetrieveDeposits retrieves the deposits of a given currency through the websocket connection.
func (e *Exchange) WSRetrieveDeposits(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getDeposits, input, &resp, true)
}
// WSRetrieveTransfers retrieves data for the requested currency through the websocket connection.
func (e *Exchange) WSRetrieveTransfers(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getTransfers, input, &resp, true)
}
// WSRetrieveCurrentDepositAddress retrieves the current deposit address for the requested currency through the websocket connection.
func (e *Exchange) WSRetrieveCurrentDepositAddress(ctx context.Context, 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 := e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrieveWithdrawals(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getWithdrawals, input, &resp, true)
}
// WsSubmitTransferBetweenSubAccounts transfer funds between two (sub)accounts.
func (e *Exchange) WsSubmitTransferBetweenSubAccounts(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, 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, e.SendWSRequest(ctx, nonMatchingEPL, submitTransferBetweenSubAccounts, input, &resp, true)
}
// WSSubmitTransferToSubAccount submits a request to transfer a currency to a subaccount
func (e *Exchange) WSSubmitTransferToSubAccount(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
}
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, e.SendWSRequest(ctx, nonMatchingEPL, submitTransferToSubaccount, input, &resp, true)
}
// WSSubmitTransferToUser submits a request to transfer a currency to another user through the websocket connection.
func (e *Exchange) WSSubmitTransferToUser(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
}
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, e.SendWSRequest(ctx, nonMatchingEPL, submitTransferToUser, input, &resp, true)
}
// ----------------------------------------------------------------------------
// WSSubmitWithdraw submits a withdrawal request to the exchange for the requested currency through the websocket connection.
func (e *Exchange) WSSubmitWithdraw(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
}
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, e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrieveAnnouncements(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getAnnouncements, input, &resp, false)
}
// WSChangeAPIKeyName changes the name of the api key requested through the websocket connection.
func (e *Exchange) WSChangeAPIKeyName(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
}
input := &struct {
ID int64 `json:"id"`
Name string `json:"name"`
}{
ID: id,
Name: name,
}
var resp *APIKeyData
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WsChangeMarginModel(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, changeMarginModel, input, &resp, true)
}
// WSChangeScopeInAPIKey changes the name of the requested subaccount id through the websocket connection.
func (e *Exchange) WSChangeScopeInAPIKey(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, changeScopeInAPIKey, input, &resp, true)
}
// WSChangeSubAccountName retrieves changes the name of the requested subaccount id through the websocket connection.
func (e *Exchange) WSChangeSubAccountName(ctx context.Context, 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 := e.SendWSRequest(ctx, 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 (e *Exchange) WSCreateAPIKey(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, createAPIKey, input, &resp, true)
}
// WSCreateSubAccount creates a new subaccount through the websocket connection.
func (e *Exchange) WSCreateSubAccount(ctx context.Context) (*SubAccountData, error) {
var resp *SubAccountData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, createSubAccount, nil, &resp, true)
}
// WSDisableAPIKey disables the api key linked to the provided id through the websocket connection.
func (e *Exchange) WSDisableAPIKey(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WsEditAPIKey(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
}
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, e.SendWSRequest(ctx, nonMatchingEPL, editAPIKey, input, &resp, true)
}
// WSEnableAffiliateProgram enables the affiliate program through the websocket connection.
func (e *Exchange) WSEnableAffiliateProgram(ctx context.Context) error {
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WSEnableAPIKey(ctx context.Context, id int64) (*APIKeyData, error) {
if id <= 0 {
return nil, fmt.Errorf("%w, invalid api key id", errInvalidID)
}
var resp *APIKeyData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, enableAPIKey, map[string]int64{"id": id}, &resp, true)
}
// WSRetrieveAccessLog lists access logs for the user through the websocket connection.
func (e *Exchange) WSRetrieveAccessLog(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getAccessLog, input, &resp, true)
}
// WSRetrieveAffiliateProgramInfo retrieves the affiliate program info through the websocket connection.
func (e *Exchange) WSRetrieveAffiliateProgramInfo(ctx context.Context) (*AffiliateProgramInfo, error) {
var resp *AffiliateProgramInfo
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getAffiliateProgramInfo, nil, &resp, true)
}
// WSRetrieveEmailLanguage retrieves the current language set for the email through the websocket connection.
func (e *Exchange) WSRetrieveEmailLanguage(ctx context.Context) (string, error) {
var resp string
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getEmailLanguage, nil, &resp, true)
}
// WSRetrieveNewAnnouncements retrieves new announcements through the websocket connection.
func (e *Exchange) WSRetrieveNewAnnouncements(ctx context.Context) ([]Announcement, error) {
var resp []Announcement
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getNewAnnouncements, nil, &resp, true)
}
// WSRetrievePosition retrieves the data of all positions in the requested instrument name through the websocket connection.
func (e *Exchange) WSRetrievePosition(ctx context.Context, instrument string) (*PositionData, error) {
if instrument == "" {
return nil, fmt.Errorf("%w, instrument_name is missing", errInvalidInstrumentName)
}
var resp *PositionData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getPosition, map[string]string{"instrument_name": instrument}, &resp, true)
}
// WSRetrieveSubAccounts retrieves all subaccounts' data through the websocket connection.
func (e *Exchange) WSRetrieveSubAccounts(ctx context.Context, withPortfolio bool) ([]SubAccountData, error) {
var resp []SubAccountData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getSubAccounts, map[string]bool{"with_portfolio": withPortfolio}, &resp, true)
}
// WSRetrieveSubAccountDetails retrieves sub-account detail information through the websocket connection.
func (e *Exchange) WSRetrieveSubAccountDetails(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getSubAccountDetails, input, &resp, true)
}
// WSRetrievePositions retrieves positions data of the user account through the websocket connection.
func (e *Exchange) WSRetrievePositions(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getPositions, input, &resp, true)
}
// WSRetrieveTransactionLog retrieves transaction logs data through the websocket connection.
func (e *Exchange) WSRetrieveTransactionLog(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getTransactionLog, input, &resp, true)
}
// WSRetrieveUserLocks retrieves information about locks on user account through the websocket connection.
func (e *Exchange) WSRetrieveUserLocks(ctx context.Context) ([]UserLock, error) {
var resp []UserLock
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getUserLocks, nil, &resp, true)
}
// WSListAPIKeys retrieves all the api keys associated with a user account through the websocket connection.
func (e *Exchange) WSListAPIKeys(ctx context.Context, tfa string) ([]APIKeyData, error) {
var resp []APIKeyData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, listAPIKeys, map[string]string{"tfa": tfa}, &resp, true)
}
// WsRetrieveCustodyAccounts retrieves user custody accounts
func (e *Exchange) WsRetrieveCustodyAccounts(ctx context.Context, ccy currency.Code) ([]CustodyAccount, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
var resp []CustodyAccount
return resp, e.SendWSRequest(ctx, nonMatchingEPL, listCustodyAccounts, &map[string]string{"currency": ccy.String()}, &resp, true)
}
// WSRemoveAPIKey removes api key vid ID through the websocket connection.
func (e *Exchange) WSRemoveAPIKey(ctx context.Context, id int64) error {
if id <= 0 {
return fmt.Errorf("%w, invalid api key id", errInvalidID)
}
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WSRemoveSubAccount(ctx context.Context, subAccountID int64) error {
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WSResetAPIKey(ctx context.Context, id int64) error {
if id <= 0 {
return fmt.Errorf("%w, invalid announcement id", errInvalidID)
}
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WSSetEmailForSubAccount(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
}
input := &struct {
SID int64 `json:"sid"`
Email string `json:"email"`
}{
Email: email,
SID: sid,
}
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WSSetEmailLanguage(ctx context.Context, language string) error {
if language == "" {
return errLanguageIsRequired
}
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WsSetSelfTradingConfig(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, setSelfTradingConfig, input, &resp, true)
}
// WSToggleNotificationsFromSubAccount toggles the notifications from a subaccount specified through the websocket connection.
func (e *Exchange) WSToggleNotificationsFromSubAccount(ctx context.Context, 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 := e.SendWSRequest(ctx, 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 (e *Exchange) WSTogglePortfolioMargining(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, togglePortfolioMargining, input, &resp, true)
}
// WSToggleSubAccountLogin toggles access for subaccount login through the websocket connection.
func (e *Exchange) WSToggleSubAccountLogin(ctx context.Context, 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 := e.SendWSRequest(ctx, 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 (e *Exchange) WSSubmitBuy(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitBuy, &arg, &resp, true)
}
// WSSubmitSell submits a sell request with the parameters provided through the websocket connection.
func (e *Exchange) WSSubmitSell(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitSell, &arg, &resp, true)
}
// WSSubmitEdit submits an edit order request through the websocket connection.
func (e *Exchange) WSSubmitEdit(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitEdit, &arg, &resp, true)
}
// WSEditOrderByLabel submits an edit order request sorted via label through the websocket connection.
func (e *Exchange) WSEditOrderByLabel(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, editByLabel, &arg, &resp, true)
}
// WSSubmitCancel sends a request to cancel the order via its orderID through the websocket connection.
func (e *Exchange) WSSubmitCancel(ctx context.Context, orderID string) (*PrivateCancelData, error) {
if orderID == "" {
return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
}
var resp *PrivateCancelData
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WSSubmitCancelAll(ctx context.Context, detailed bool) (*MultipleCancelResponse, error) {
var resp *MultipleCancelResponse
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WSSubmitCancelAllByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitCancelAllByCurrency, input, &resp, true)
}
// WSSubmitCancelAllByInstrument sends a request to cancel all user orders for the specified instrument through the websocket connection.
func (e *Exchange) WSSubmitCancelAllByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WsSubmitCancelAllByKind(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitCancelAllByKind, input, &resp, true)
}
// WSSubmitCancelByLabel sends a request to cancel all user orders for the specified label through the websocket connection.
func (e *Exchange) WSSubmitCancelByLabel(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WSSubmitCancelQuotes(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitCancelQuotes, input, &resp, true)
}
// WSSubmitClosePosition sends a request to cancel all user orders for the specified label through the websocket connection.
func (e *Exchange) WSSubmitClosePosition(ctx context.Context, 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, e.SendWSRequest(ctx, matchingEPL, submitClosePosition, input, &resp, true)
}
// WSRetrieveMargins sends a request to fetch account margins data through the websocket connection.
func (e *Exchange) WSRetrieveMargins(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getMargins, input, &resp, true)
}
// WSRetrieveMMPConfig sends a request to fetch the config for MMP of the requested currency through the websocket connection.
func (e *Exchange) WSRetrieveMMPConfig(ctx context.Context, ccy currency.Code) (*MMPConfigData, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
var resp *MMPConfigData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getMMPConfig, map[string]currency.Code{"currency": ccy}, &resp, true)
}
// WSRetrieveOpenOrdersByCurrency retrieves open order by symbol and kind
func (e *Exchange) WSRetrieveOpenOrdersByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOpenOrdersByCurrency, input, &resp, true)
}
// WSRetrieveOpenOrdersByLabel retrieves open order by label and currency
func (e *Exchange) WSRetrieveOpenOrdersByLabel(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOpenOrdersByLabel, input, &resp, true)
}
// WSRetrieveOpenOrdersByInstrument sends a request to fetch open orders data sorted by requested params through the websocket connection.
func (e *Exchange) WSRetrieveOpenOrdersByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOpenOrdersByInstrument, input, &resp, true)
}
// WSRetrieveOrderHistoryByCurrency sends a request to fetch order history according to given params and currency through the websocket connection.
func (e *Exchange) WSRetrieveOrderHistoryByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOrderHistoryByCurrency, input, &resp, true)
}
// WSRetrieveOrderHistoryByInstrument sends a request to fetch order history according to given params and instrument through the websocket connection.
func (e *Exchange) WSRetrieveOrderHistoryByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOrderHistoryByInstrument, input, &resp, true)
}
// WSRetrieveOrderMarginsByID sends a request to fetch order margins data according to their ids through the websocket connection.
func (e *Exchange) WSRetrieveOrderMarginsByID(ctx context.Context, ids []string) ([]OrderData, error) {
if len(ids) == 0 {
return nil, fmt.Errorf("%w, order ids cannot be empty", errInvalidID)
}
var resp []OrderData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getOrderMarginByIDs, map[string][]string{"ids": ids}, &resp, true)
}
// WSRetrievesOrderState sends a request to fetch order state of the order id provided
func (e *Exchange) WSRetrievesOrderState(ctx context.Context, orderID string) (*OrderData, error) {
if orderID == "" {
return nil, fmt.Errorf("%w, no order ID specified", errInvalidID)
}
var resp *OrderData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getOrderState, map[string]string{"order_id": orderID}, &resp, true)
}
// WsRetrieveOrderStateByLabel retrieves an order state by label and currency
func (e *Exchange) WsRetrieveOrderStateByLabel(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getOrderStateByLabel, input, &resp, true)
}
// WSRetrieveTriggerOrderHistory sends a request to fetch order state of the order id provided through the websocket connection.
func (e *Exchange) WSRetrieveTriggerOrderHistory(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getTriggerOrderHistory, input, &resp, true)
}
// WSRetrieveUserTradesByCurrency sends a request to fetch user trades sorted by currency through the websocket connection.
func (e *Exchange) WSRetrieveUserTradesByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getUserTradesByCurrency, input, &resp, true)
}
// WSRetrieveUserTradesByCurrencyAndTime retrieves user trades sorted by currency and time through the websocket connection.
func (e *Exchange) WSRetrieveUserTradesByCurrencyAndTime(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getUserTradesByCurrencyAndTime, input, &resp, true)
}
// WsRetrieveUserTradesByInstrument retrieves user trades sorted by instrument through the websocket connection.
func (e *Exchange) WsRetrieveUserTradesByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getUserTradesByInstrument, input, &resp, true)
}
// WSRetrieveUserTradesByInstrumentAndTime retrieves user trades sorted by instrument and time through the websocket connection.
func (e *Exchange) WSRetrieveUserTradesByInstrumentAndTime(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getUserTradesByInstrumentAndTime, input, &resp, true)
}
// WSRetrieveUserTradesByOrder retrieves user trades fetched by orderID through the web socket connection.
func (e *Exchange) WSRetrieveUserTradesByOrder(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getUserTradesByOrder, input, &resp, true)
}
// WSResetMMP sends a request to reset MMP for a currency provided through the websocket connection.
func (e *Exchange) WSResetMMP(ctx context.Context, ccy currency.Code) error {
if ccy.IsEmpty() {
return currency.ErrCurrencyCodeEmpty
}
var resp string
err := e.SendWSRequest(ctx, 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
}
// WSSetMMPConfig sends a request to set the given parameter values to the mmp config for the provided currency through the websocket connection.
func (e *Exchange) WSSetMMPConfig(ctx context.Context, ccy currency.Code, interval kline.Interval, frozenTime int64, quantityLimit, deltaLimit float64) error {
if ccy.IsEmpty() {
return currency.ErrCurrencyCodeEmpty
}
params := make(map[string]any)
params["currency"] = ccy
intervalString, err := e.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 = e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrieveSettlementHistoryByInstrument(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getSettlementHistoryByInstrument, input, &resp, true)
}
// WSRetrieveSettlementHistoryByCurency sends a request to fetch settlement history data sorted by currency through the websocket connection.
func (e *Exchange) WSRetrieveSettlementHistoryByCurency(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrieveComboIDs(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getComboIDs, input, &resp, false)
}
// WSRetrieveComboDetails retrieves information about a combo through the websocket connection.
func (e *Exchange) WSRetrieveComboDetails(ctx context.Context, comboID string) (*ComboDetail, error) {
if comboID == "" {
return nil, errInvalidComboID
}
var resp *ComboDetail
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getComboDetails, map[string]string{"combo_id": comboID}, &resp, false)
}
// WSRetrieveCombos retrieves information about active combos through the websocket connection.
func (e *Exchange) WSRetrieveCombos(ctx context.Context, ccy currency.Code) ([]ComboDetail, error) {
if ccy.IsEmpty() {
return nil, currency.ErrCurrencyCodeEmpty
}
var resp []ComboDetail
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WSCreateCombo(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, createCombos, map[string]any{"trades": args}, &resp, true)
}
// WsLogout gracefully close websocket connection, when COD (Cancel On Disconnect) is enabled orders are not cancelled
func (e *Exchange) WsLogout(ctx context.Context, invalidateToken bool) error {
input := struct {
InvalidateToken bool `json:"invalidate_token,omitempty"`
}{
InvalidateToken: invalidateToken,
}
return e.SendWSRequest(ctx, 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 (e *Exchange) WsEnableCancelOnDisconnect(ctx context.Context, scope string) (string, error) {
input := &struct {
Scope string `json:"scope,omitempty"`
}{
Scope: scope,
}
var resp string
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WsDisableCancelOnDisconnect(ctx context.Context, scope string) (string, error) {
input := &struct {
Scope string `json:"scope,omitempty"`
}{
Scope: scope,
}
var resp string
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) SayHello(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WsRetrieveCancelOnDisconnect(ctx context.Context, scope string) (*CancelOnDisconnect, error) {
input := &struct {
Scope string `json:"scope,omitempty"`
}{
Scope: scope,
}
var resp *CancelOnDisconnect
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WsExchangeToken(ctx context.Context, 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, e.SendWSRequest(ctx, 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 (e *Exchange) WsForkToken(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, "public/fork_token", input, &resp, true)
}
// UnsubscribeAll unsubscribe from all the public channels subscribed so far.
func (e *Exchange) UnsubscribeAll(ctx context.Context) (string, error) {
var resp string
return resp, e.SendWSRequest(ctx, nonMatchingEPL, "public/unsubscribe_all", nil, &resp, false)
}
// UnsubscribeAllPrivateChannels sends an unsubscribe request to cancel all private channels subscriptions
func (e *Exchange) UnsubscribeAllPrivateChannels(ctx context.Context) (string, error) {
var resp string
return resp, e.SendWSRequest(ctx, 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 (e *Exchange) WSExecuteBlockTrade(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 := e.WSVerifyBlockTrade(ctx, 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, e.SendWSRequest(ctx, matchingEPL, executeBlockTrades, input, &resp, true)
}
// WSVerifyBlockTrade verifies and creates block trade signature through the websocket connection.
func (e *Exchange) WSVerifyBlockTrade(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
}
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, e.SendWSRequest(ctx, 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 (e *Exchange) WsInvalidateBlockTradeSignature(ctx context.Context, signature string) error {
if signature == "" {
return errMissingSignature
}
params := url.Values{}
params.Set("signature", signature)
var resp string
err := e.SendWSRequest(ctx, 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 (e *Exchange) WSRetrieveUserBlockTrade(ctx context.Context, id string) ([]BlockTradeData, error) {
if id == "" {
return nil, errMissingBlockTradeID
}
var resp []BlockTradeData
return resp, e.SendWSRequest(ctx, nonMatchingEPL, getBlockTrades, map[string]string{"id": id}, &resp, true)
}
// WSRetrieveLastBlockTradesByCurrency returns list of last users block trades through the websocket connection.
func (e *Exchange) WSRetrieveLastBlockTradesByCurrency(ctx context.Context, 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, e.SendWSRequest(ctx, nonMatchingEPL, getLastBlockTradesByCurrency, input, &resp, true)
}
// WSMovePositions moves positions from source subaccount to target subaccount through the websocket connection.
func (e *Exchange) WSMovePositions(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)
}
}
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, e.SendWSRequest(ctx, nonMatchingEPL, movePositions, input, &resp, true)
}
// WsSimulateBlockTrade checks if a block trade can be executed through the websocket
func (e *Exchange) WsSimulateBlockTrade(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)
}
}
input := &struct {
Role string `json:"role"`
Trades []BlockTradeParam `json:"trades"`
}{
Role: role,
Trades: trades,
}
var resp bool
return resp, e.SendWSRequest(ctx, matchingEPL, simulateBlockPosition, input, resp, true)
}
// SendWSRequest sends a request through the websocket connection.
// both authenticated and public endpoints are allowed.
func (e *Exchange) SendWSRequest(ctx context.Context, epl request.EndpointLimit, method string, params, response any, authenticated bool) error {
if authenticated && !e.Websocket.CanUseAuthenticatedEndpoints() {
return errWebsocketConnectionNotAuthenticated
}
input := &WsRequest{
JSONRPCVersion: rpcVersion,
ID: e.MessageID(),
Method: method,
Params: params,
}
resp := &wsResponse{Result: response}
err := e.sendWsPayload(ctx, 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
// TODO: Refactor to use rate limiting system
func (e *Exchange) sendWsPayload(ctx context.Context, 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(ctx, deadline)
defer func() {
if time.Now().After(deadline) {
cancelFunc()
}
}()
for attempt := 1; ; attempt++ {
// Initiate a rate limit reservation and sleep on requested endpoint
err := e.Requester.InitiateRateLimit(ctx, ep)
if err != nil {
return fmt.Errorf("failed to rate limit Websocket request: %w", err)
}
if e.Verbose {
log.Debugf(log.RequestSys, "%s attempt %d", e.Name, attempt)
}
var payload []byte
payload, err = e.Websocket.Conn.SendMessageReturnResponse(ctx, request.Unset, 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 := max(after, backoff)
if dl, ok := ctx.Deadline(); ok && dl.Before(time.Now().Add(delay)) {
return errors.New("deadline would be exceeded by retry")
}
if e.Verbose {
log.Errorf(log.RequestSys,
"%s request has failed. Retrying request in %s, attempt %d",
e.Name,
delay,
attempt)
}
time.Sleep(delay)
continue
default:
return nil
}
}
}