mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
* deribit implementation * add ws impll * cleanup * Update deribit_wrapper.go * Add missing endpoints * Fix config file * asset type update * Update code structure * Update authenticated private endpoints unit tests * Updating websocket * Updating websocket connection and subscription handling * Finishing up adding subscription push data * Adding websocket public endpoint * Adding WS endpoints * Adding websocket unit tests * Minor clean-up * Integrating websocket endpoints into the wrapper funcs * Updating exchange documentations * Fixing test issues * Code cleaning-up * fix test issues * Updating validations and logic errors * Updating wrapper issues * fix test issues * Slight test update * Unit test and code structure update * Update websocket tempos * Slight update on code structure * Minor update on unit tests * Update depending on review comments * Minor code fix and doc re-generating * Update on Candlestick wrapper functions * Minor updates * minor unit test updates * Minor updates on weboscket and unit tests * minor linter fix * codespell and rate limiter issues * single linter issue fix * adding rate limiter * Add ratelimiter to websocket conn and overall code update * fix websocket push data issue * Implementing missing wrapper function * Websocket fix * Minor update on missing endpoint and other * fixing websocket issues and cleaningup * Minor tempo fix * Minor linter issues * unit test update * Indexing error fix * Websocket connection fix * string formatting fix * Small fix on unit tests * fix minor json conversion issue * websocket and documentation update * websocket, wrapper and unit test updates * Documentation and unit tests update * Fix unit tests * wrapper fix for new change * Unit test fix * timestamp conversion and unit tests update * Minor instrument ID conversion fix * instrument formats and unit test update * formatting and unit test fix * config update * Updating websocket and adding the Spot support * Add small unit test fix * unit test and websocket handlers update * Linter issues fix * minor documentation and code update * Minor fix * added a wrapper func GetLatestFundingRates * Types, wrapper update, and unit tests * Minor config update * fix wrapper unit tests * Resolve all panic and wrapper test issues * minor unit test fix * fix issues and adding newly added endpoints * updates and added remaining endpoints with unit tests * Update unit tests using assert * Added missing endpoints and unit tests * Minor updates and clean-ups * Resolve tradable pair fetching panic * Mutex fix * Added Options assets test and minor fixes * subscription mothod updated * Remove misadded code lines * resolve websocket * Updating tests, types, endpoint methods and others * Added GetFuturesContractDetails and minor fix * fix linter issue * revert change on candlestic time * Added filters to candles * minor unit test and wrapper fix * Minor unit tests update * cahnge param key for GetOrderMarginByID * updating unit tests and resolve issues * Update websocket unit tests * Minor fix based on review * Revert unit test change * fix pair config issue * Added missing wrapper functions * Fix missing review changes * Fix options request pair formatting * fix AllExchangeWrappers test issue * Changes with unit test and wrapper based on the review * Fix to options reg-exp * wrapper functions fix * Update MaximumFundingRateHistory filter and minor fixes * Fix besed on review comment * Fix issues on review comment * linter fix * fix minor unit test issue * Fix unit test issues * Update trade order cancellation responses * fix config files issue * lint update config files * Update unit tests * Update return values and response handling * added missing endpoint and fixes based on review comment * toggle useTestNet back * Update cancel by label and other fix * fix forgotten cancel all response type * update CancelResp type * Fix unmarshaling error * updated websocket orderbook load issue * fix websocket lock and groups * Change Items to Tranche and fix linter issues * Fix orderbook issue * Update unit tests offline error handling, and endpoints argument and error handling * Contributors documentation update and change error return type * Updated unit tests based on review comment * Update unit tests and removed password change endpoint * Fix race condition * Update on tests, test pairs, and wrapper config * Update test tradable pairs loading * Update unit tests, fix linter issues, and update wrapper functions * remove credentials * Update test and fix authentication method and few authenticated endpoints * fix codespell issue * group the repeated currency code check to a func * added unit test for repeated pair check func * Added a base coin and related updates --------- Co-authored-by: E Sequeira <earncef@earncef.com>
1602 lines
53 KiB
Go
1602 lines
53 KiB
Go
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()
|
|
}
|