FTX: Margin lending/borrow rate history (#981)

* Adds lending rates/borrows to FTX and the command

* Movements, renames, rpc test

* Fleshing out rpc response

* Allows rpcserver to calculate offline (but not gctcli). Expands tests

* rn structs. add exchange_wrapper_issues support

* Adds a nice yearly rate

* Surprise yearly borrow rate!

* Rn+Mv to margin package. Fixes some serious whoopsies

* Adds average lend/borrow rates instead of sum

* rm oopsie whoopsie

* This is what the linter was having an issue with

* re-gen

* lintl

* niteroos
This commit is contained in:
Scott
2022-07-12 14:27:35 +10:00
committed by GitHub
parent e02053a2d6
commit bed9425a08
21 changed files with 3135 additions and 989 deletions

View File

@@ -17,6 +17,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
@@ -1319,3 +1320,8 @@ func (b *Base) HasAssetTypeAccountSegregation() bool {
func (b *Base) GetServerTime(_ context.Context, _ asset.Item) (time.Time, error) {
return time.Time{}, common.ErrNotYetImplemented
}
// GetMarginRatesHistory returns the margin rate history for the supplied currency
func (b *Base) GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error) {
return nil, common.ErrNotYetImplemented
}

View File

@@ -2328,3 +2328,11 @@ func TestGetServerTime(t *testing.T) {
t.Errorf("received: %v, expected: %v", err, common.ErrNotYetImplemented)
}
}
func TestGetFundingRateHistory(t *testing.T) {
t.Parallel()
var b Base
if _, err := b.GetMarginRatesHistory(context.Background(), nil); !errors.Is(err, common.ErrNotYetImplemented) {
t.Errorf("received: %v, expected: %v", err, common.ErrNotYetImplemented)
}
}

View File

@@ -398,7 +398,7 @@ func (f *FTX) GetMarginLendingRates(ctx context.Context) ([]MarginFundingData, e
r := struct {
Data []MarginFundingData `json:"result"`
}{}
return r.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, marginLendingRates, nil, &r)
return r.Data, f.SendHTTPRequest(ctx, exchange.RestSpot, marginLendingRates, &r)
}
// MarginDailyBorrowedAmounts gets daily borrowed amounts for margin
@@ -435,7 +435,7 @@ func (f *FTX) GetMarginBorrowHistory(ctx context.Context, startTime, endTime tim
return r.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, endpoint, nil, &r)
}
// GetMarginMarketLendingHistory gets the markets margin lending rate history
// GetMarginMarketLendingHistory gets the market's margin lending rate history
func (f *FTX) GetMarginMarketLendingHistory(ctx context.Context, coin currency.Code, startTime, endTime time.Time) ([]MarginTransactionHistoryData, error) {
r := struct {
Data []MarginTransactionHistoryData `json:"result"`
@@ -452,7 +452,7 @@ func (f *FTX) GetMarginMarketLendingHistory(ctx context.Context, coin currency.C
params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10))
}
endpoint := common.EncodeURLValues(marginLendingHistory, params)
return r.Data, f.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, endpoint, params, &r)
return r.Data, f.SendHTTPRequest(ctx, exchange.RestSpot, endpoint, &r)
}
// GetMarginLendingHistory gets margin lending history

View File

@@ -17,6 +17,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
@@ -349,9 +350,6 @@ func TestGetMarginBorrowRates(t *testing.T) {
func TestGetMarginLendingRates(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip()
}
_, err := f.GetMarginLendingRates(context.Background())
if err != nil {
t.Error(err)
@@ -409,9 +407,6 @@ func TestGetMarginMarketLendingHistory(t *testing.T) {
t.Errorf("expected %s, got %s", errStartTimeCannotBeAfterEndTime, err)
}
if !areTestAPIKeysSet() {
t.Skip("api keys not set")
}
_, err = f.GetMarginMarketLendingHistory(context.Background(),
currency.USD, tmNow.AddDate(0, 0, -1), tmNow)
if err != nil {
@@ -2197,3 +2192,275 @@ func TestGetCollateral(t *testing.T) {
t.Error(err)
}
}
func TestGetMarginRatesHistory(t *testing.T) {
t.Parallel()
type testCase struct {
name string
request *margin.RateHistoryRequest
err error
requiresAuth bool
}
tests := []testCase{
{
name: "nil request",
request: nil,
err: common.ErrNilPointer,
},
{
name: "empty request",
request: &margin.RateHistoryRequest{},
err: currency.ErrCurrencyCodeEmpty,
},
{
name: "disabled currency request",
request: &margin.RateHistoryRequest{
Asset: asset.Futures,
Currency: currency.LUNA,
},
err: currency.ErrCurrencyNotFound,
},
{
name: "empty date request",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
},
err: common.ErrDateUnset,
},
{
name: "nice basic request",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
},
err: nil,
},
{
name: "include predicted rate",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
GetPredictedRate: true,
},
err: nil,
},
{
name: "include borrowed rates",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
GetBorrowRates: true,
},
err: nil,
requiresAuth: true,
},
{
name: "include predicted borrowed rates",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
GetBorrowRates: true,
GetPredictedRate: true,
},
err: nil,
requiresAuth: true,
},
{
name: "all you can eat",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 365 * 2),
EndDate: time.Now(),
GetBorrowRates: true,
GetPredictedRate: true,
GetLendingPayments: true,
GetBorrowCosts: true,
},
err: nil,
requiresAuth: true,
},
{
name: "offline failure, no rates",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
},
err: common.ErrCannotCalculateOffline,
},
{
name: "offline failure, no fee for lending",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
GetLendingPayments: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
},
err: common.ErrCannotCalculateOffline,
},
{
name: "offline failure, no fee for borrow",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
GetBorrowCosts: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
},
err: common.ErrCannotCalculateOffline,
},
{
name: "offline pass, lending w fee",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
GetLendingPayments: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
},
err: nil,
},
{
name: "offline pass, borrow w fee",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
GetBorrowCosts: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
BorrowCost: margin.BorrowCost{Size: decimal.NewFromFloat(1337)},
},
},
},
err: nil,
},
{
name: "offline pass, lending size",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
GetLendingPayments: true,
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
LendingPayment: margin.LendingPayment{Size: decimal.NewFromFloat(1337)},
},
},
},
err: nil,
},
{
name: "offline failure, cannot predict offline",
request: &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 7),
EndDate: time.Now(),
CalculateOffline: true,
TakeFeeRate: decimal.NewFromFloat(0.01),
Rates: []margin.Rate{
{
Time: time.Now().Add(-time.Hour),
HourlyRate: decimal.NewFromInt(1337),
},
},
GetPredictedRate: true,
},
err: common.ErrCannotCalculateOffline,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if tt.requiresAuth && !areTestAPIKeysSet() {
t.Skip("requires auth")
}
_, err := f.GetMarginRatesHistory(context.Background(), tt.request)
if !errors.Is(err, tt.err) {
t.Errorf("receieved '%v' expected '%v'", err, tt.err)
}
})
}
if !areTestAPIKeysSet() {
return
}
// test offline calculation against real data
online, err := f.GetMarginRatesHistory(context.Background(), &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 2),
EndDate: time.Now(),
GetBorrowRates: true,
GetBorrowCosts: true,
})
if !errors.Is(err, nil) {
t.Errorf("receieved '%v' expected '%v'", err, nil)
}
offline, err := f.GetMarginRatesHistory(context.Background(), &margin.RateHistoryRequest{
Asset: asset.Spot,
Currency: currency.USD,
StartDate: time.Now().Add(-time.Hour * 24 * 2),
EndDate: time.Now(),
GetBorrowRates: true,
GetBorrowCosts: true,
Rates: online.Rates,
TakeFeeRate: online.TakerFeeRate,
CalculateOffline: true,
})
if !errors.Is(err, nil) {
t.Errorf("receieved '%v' expected '%v'", err, nil)
}
if !online.Rates[0].BorrowCost.Cost.Equal(offline.Rates[0].BorrowCost.Cost) {
t.Errorf("expected '%v' received '%v'", online.Rates[0].BorrowCost.Cost, offline.Rates[0].BorrowCost.Cost)
}
}

View File

@@ -10,9 +10,9 @@ import (
// MarginFundingData stores borrowing/lending data for margin trading
type MarginFundingData struct {
Coin string `json:"coin"`
Estimate float64 `json:"estimate"`
Previous float64 `json:"previous"`
Coin currency.Code `json:"coin"`
Estimate float64 `json:"estimate"`
Previous float64 `json:"previous"`
}
// MarginDailyBorrowStats stores the daily borrowed amounts
@@ -32,11 +32,12 @@ type MarginMarketInfo struct {
// MarginTransactionHistoryData stores margin borrowing/lending history
type MarginTransactionHistoryData struct {
Coin string `json:"coin"`
Cost float64 `json:"cost"`
Rate float64 `json:"rate"`
Size float64 `json:"size"`
Time time.Time `json:"time"`
Coin currency.Code `json:"coin"`
Cost float64 `json:"cost"`
Rate float64 `json:"rate"`
Size float64 `json:"size"`
Proceeds float64 `json:"proceeds"`
Time time.Time `json:"time"`
}
// LendingOffersData stores data for lending offers
@@ -748,17 +749,6 @@ type WsFillsDataStore struct {
// TimeInterval represents interval enum.
type TimeInterval string
// Vars related to time intervals
var (
TimeIntervalFifteenSeconds = TimeInterval("15")
TimeIntervalMinute = TimeInterval("60")
TimeIntervalFiveMinutes = TimeInterval("300")
TimeIntervalFifteenMinutes = TimeInterval("900")
TimeIntervalHour = TimeInterval("3600")
TimeIntervalFourHours = TimeInterval("14400")
TimeIntervalDay = TimeInterval("86400")
)
// OrderVars stores side, status and type for any order/trade
type OrderVars struct {
Side order.Side

View File

@@ -20,6 +20,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
@@ -1693,3 +1694,224 @@ func (f *FTX) GetCollateralCurrencyForContract(_ asset.Item, _ currency.Pair) (c
func (f *FTX) GetCurrencyForRealisedPNL(_ asset.Item, _ currency.Pair) (currency.Code, asset.Item, error) {
return currency.USD, asset.Spot, nil
}
// GetMarginRatesHistory gets the margin rate history for the given currency, asset, pair
// Can also include borrow rates, or lending income/borrow payments
func (f *FTX) GetMarginRatesHistory(ctx context.Context, request *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error) {
if request == nil {
return nil, fmt.Errorf("%w funding rate request is nil", common.ErrNilPointer)
}
if request.Currency.IsEmpty() {
return nil, fmt.Errorf("%w funding rate request is empty", currency.ErrCurrencyCodeEmpty)
}
pairs, err := f.GetEnabledPairs(request.Asset)
if err != nil {
return nil, err
}
if !pairs.ContainsCurrency(request.Currency) {
return nil, fmt.Errorf("%w '%v' in enabled pairs", currency.ErrCurrencyNotFound, request.Currency)
}
err = common.StartEndTimeCheck(request.StartDate, request.EndDate)
if err != nil {
return nil, err
}
var (
one = decimal.NewFromInt(1)
fiveHundred = decimal.NewFromInt(500)
twentyFour = decimal.NewFromInt(24)
threeSixFive = decimal.NewFromInt(365)
takerFeeRate, averageBorrowSize, averageLendSize decimal.Decimal
borrowSizeLen, lendSizeLen int64
)
switch {
case request.CalculateOffline:
takerFeeRate = request.TakeFeeRate
case request.GetBorrowRates:
var accountInfo AccountInfoData
accountInfo, err = f.GetAccountInfo(ctx)
if err != nil {
return nil, err
}
takerFeeRate = decimal.NewFromFloat(accountInfo.TakerFee)
}
response := &margin.RateHistoryResponse{
TakerFeeRate: takerFeeRate,
}
if request.CalculateOffline {
if len(request.Rates) == 0 {
return nil, fmt.Errorf("%w calculation requires rates", common.ErrCannotCalculateOffline)
}
response.Rates = request.Rates
} else {
var responseRates []margin.Rate
endDate := request.EndDate
for {
var rates []MarginTransactionHistoryData
rates, err = f.GetMarginMarketLendingHistory(ctx, request.Currency, request.StartDate, endDate)
if err != nil {
return nil, err
}
if len(rates) == 0 || rates[len(rates)-1].Time.Equal(endDate) {
break
}
for i := range rates {
if !rates[i].Coin.Equal(request.Currency) {
continue
}
rate := margin.Rate{
Time: rates[i].Time,
HourlyRate: decimal.NewFromFloat(rates[i].Rate),
MarketBorrowSize: decimal.NewFromFloat(rates[i].Size),
}
rate.YearlyRate = rate.HourlyRate.Mul(twentyFour.Mul(threeSixFive))
if request.GetBorrowRates {
rate.HourlyBorrowRate = rate.HourlyRate.Mul(one.Add(fiveHundred.Mul(takerFeeRate)))
rate.YearlyBorrowRate = rate.HourlyBorrowRate.Mul(twentyFour.Mul(threeSixFive))
}
responseRates = append(responseRates, rate)
}
if rates[len(rates)-1].Time.Before(request.StartDate) {
break
}
endDate = rates[len(rates)-1].Time
}
if len(responseRates) == 0 {
return nil, fmt.Errorf("%w no rates returned between %v-%v", common.ErrNoResponse, request.StartDate, request.EndDate)
}
sort.Slice(responseRates, func(i, j int) bool {
return responseRates[i].Time.Before(responseRates[j].Time)
})
response.Rates = responseRates
}
if request.GetPredictedRate {
if request.CalculateOffline {
return nil, fmt.Errorf("%w predicted rate is online only", common.ErrCannotCalculateOffline)
}
var borrowRates []MarginFundingData
borrowRates, err = f.GetMarginLendingRates(ctx)
if err != nil {
return nil, err
}
for i := range borrowRates {
if !borrowRates[i].Coin.Equal(request.Currency) {
continue
}
response.PredictedRate = margin.Rate{
Time: response.Rates[len(response.Rates)-1].Time.Add(time.Hour),
HourlyRate: decimal.NewFromFloat(borrowRates[i].Estimate),
}
response.PredictedRate.YearlyRate = response.PredictedRate.HourlyRate.Mul(twentyFour.Mul(threeSixFive))
if request.GetBorrowRates {
response.PredictedRate.HourlyBorrowRate = response.PredictedRate.HourlyRate.Mul(one.Add(fiveHundred.Mul(takerFeeRate)))
response.PredictedRate.YearlyBorrowRate = response.PredictedRate.HourlyBorrowRate.Mul(twentyFour.Mul(threeSixFive))
}
}
}
if request.GetLendingPayments {
if request.CalculateOffline {
if request.TakeFeeRate.IsZero() {
return nil, fmt.Errorf("%w taker fee unset", common.ErrCannotCalculateOffline)
}
for i := range request.Rates {
response.Rates[i].LendingPayment.Payment = response.Rates[i].HourlyRate.Mul(response.Rates[i].LendingPayment.Size)
response.SumLendingPayments = response.SumLendingPayments.Add(response.Rates[i].LendingPayment.Payment)
averageLendSize = averageLendSize.Add(response.Rates[i].LendingPayment.Size)
lendSizeLen++
}
} else {
endDate := request.EndDate
for {
var payments []MarginTransactionHistoryData
payments, err = f.GetMarginLendingHistory(ctx, request.Currency, request.StartDate, endDate)
if err != nil {
return nil, err
}
if len(payments) == 0 || payments[len(payments)-1].Time.Equal(endDate) {
break
}
for i := range payments {
if !payments[i].Coin.Equal(request.Currency) {
continue
}
for j := range response.Rates {
if !response.Rates[j].Time.Equal(payments[i].Time) {
continue
}
response.Rates[j].LendingPayment.Payment = decimal.NewFromFloat(payments[i].Proceeds)
response.Rates[j].LendingPayment.Size = decimal.NewFromFloat(payments[i].Size)
response.SumLendingPayments = response.SumLendingPayments.Add(response.Rates[j].LendingPayment.Payment)
averageLendSize = averageLendSize.Add(response.Rates[j].LendingPayment.Size)
lendSizeLen++
break
}
}
if payments[len(payments)-1].Time.Before(request.StartDate) {
break
}
endDate = payments[len(payments)-1].Time
}
}
}
if request.GetBorrowCosts {
if request.CalculateOffline {
if request.TakeFeeRate.IsZero() {
return nil, fmt.Errorf("%w taker fee unset", common.ErrCannotCalculateOffline)
}
for i := range request.Rates {
response.Rates[i].HourlyBorrowRate = response.Rates[i].HourlyRate.Mul(one.Add(fiveHundred.Mul(takerFeeRate)))
response.Rates[i].YearlyBorrowRate = response.Rates[i].HourlyBorrowRate.Mul(one.Add(fiveHundred.Mul(takerFeeRate)))
response.Rates[i].BorrowCost.Cost = response.Rates[i].HourlyBorrowRate.Mul(response.Rates[i].BorrowCost.Size)
response.SumBorrowCosts = response.SumBorrowCosts.Add(response.Rates[i].BorrowCost.Cost)
averageBorrowSize = averageBorrowSize.Add(response.Rates[i].BorrowCost.Size)
borrowSizeLen++
}
} else {
endDate := request.EndDate
for {
var costs []MarginTransactionHistoryData
costs, err = f.GetMarginBorrowHistory(ctx, request.StartDate, endDate)
if err != nil {
return nil, err
}
if len(costs) == 0 || costs[len(costs)-1].Time.Equal(endDate) {
break
}
for i := range costs {
if !costs[i].Coin.Equal(request.Currency) {
continue
}
for j := range response.Rates {
if !response.Rates[j].Time.Equal(costs[i].Time) {
continue
}
response.Rates[j].BorrowCost.Cost = decimal.NewFromFloat(costs[i].Cost)
response.Rates[j].BorrowCost.Size = decimal.NewFromFloat(costs[i].Size)
response.SumBorrowCosts = response.SumBorrowCosts.Add(response.Rates[j].BorrowCost.Cost)
averageBorrowSize = averageBorrowSize.Add(response.Rates[j].BorrowCost.Size)
borrowSizeLen++
break
}
}
if costs[len(costs)-1].Time.Before(request.StartDate) {
break
}
endDate = costs[len(costs)-1].Time
}
}
}
if borrowSizeLen > 0 {
response.AverageBorrowSize = averageBorrowSize.Div(decimal.NewFromInt(borrowSizeLen))
}
if lendSizeLen > 0 {
response.AverageLendingSize = averageLendSize.Div(decimal.NewFromInt(lendSizeLen))
}
return response, nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/currencystate"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/margin"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stream"
@@ -66,6 +67,7 @@ type IBotExchange interface {
EnableRateLimiter() error
GetServerTime(ctx context.Context, ai asset.Item) (time.Time, error)
CurrencyStateManagement
GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error)
order.PNLCalculation
order.CollateralManagement

View File

@@ -0,0 +1,67 @@
package margin
import (
"time"
"github.com/shopspring/decimal"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)
// RateHistoryRequest is used to request a funding rate
type RateHistoryRequest struct {
Exchange string
Asset asset.Item
Currency currency.Code
StartDate time.Time
EndDate time.Time
GetPredictedRate bool
GetLendingPayments bool
GetBorrowRates bool
GetBorrowCosts bool
// CalculateOffline allows for the borrow rate, lending payment amount
// and borrow costs to be calculated offline. It requires the takerfeerate
// and existing rates
CalculateOffline bool
TakeFeeRate decimal.Decimal
// Rates is used when calculating offline and determiningPayments
// Each Rate must have the Rate and Size fields populated
Rates []Rate
}
// RateHistoryResponse has the funding rate details
type RateHistoryResponse struct {
Rates []Rate
SumBorrowCosts decimal.Decimal
AverageBorrowSize decimal.Decimal
SumLendingPayments decimal.Decimal
AverageLendingSize decimal.Decimal
PredictedRate Rate
TakerFeeRate decimal.Decimal
}
// Rate has the funding rate details
// and optionally the borrow rate
type Rate struct {
Time time.Time
MarketBorrowSize decimal.Decimal
HourlyRate decimal.Decimal
YearlyRate decimal.Decimal
HourlyBorrowRate decimal.Decimal
YearlyBorrowRate decimal.Decimal
LendingPayment LendingPayment
BorrowCost BorrowCost
}
// LendingPayment contains a lending rate payment
type LendingPayment struct {
Payment decimal.Decimal
Size decimal.Decimal
}
// BorrowCost contains the borrow rate costs
type BorrowCost struct {
Cost decimal.Decimal
Size decimal.Decimal
}

View File

@@ -23,7 +23,10 @@ var (
ErrAmountIsInvalid = errors.New("order amount is equal or less than zero")
ErrPriceMustBeSetIfLimitOrder = errors.New("order price must be set if limit order type is desired")
ErrOrderIDNotSet = errors.New("order id or client order id is not set")
errCannotLiquidate = errors.New("cannot liquidate position")
// ErrNoRates is returned when no margin rates are returned when they are expected
ErrNoRates = errors.New("no rates")
errCannotLiquidate = errors.New("cannot liquidate position")
)
// Submit contains all properties of an order that may be required