mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-24 07:26:47 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
67
exchanges/margin/margin_types.go
Normal file
67
exchanges/margin/margin_types.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user