mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +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:
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
|
||||
"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/orderbook"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
||||
@@ -4361,7 +4362,7 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkParams(r.Exchange, exch, a, currency.Pair{})
|
||||
err = checkParams(r.Exchange, exch, a, currency.EMPTYPAIR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -4749,3 +4750,182 @@ func (s *RPCServer) GetTechnicalAnalysis(ctx context.Context, r *gctrpc.GetTechn
|
||||
|
||||
return &gctrpc.GetTechnicalAnalysisResponse{Signals: signals}, nil
|
||||
}
|
||||
|
||||
// GetMarginRatesHistory returns the margin lending or borrow rates for an exchange, asset, currency along with many customisable options
|
||||
func (s *RPCServer) GetMarginRatesHistory(ctx context.Context, r *gctrpc.GetMarginRatesHistoryRequest) (*gctrpc.GetMarginRatesHistoryResponse, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("%w GetLendingRatesRequest", common.ErrNilPointer)
|
||||
}
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a, err := asset.New(r.Asset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkParams(r.Exchange, exch, a, currency.EMPTYPAIR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := currency.NewCode(r.Currency)
|
||||
pairs, err := exch.GetEnabledPairs(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !pairs.ContainsCurrency(c) {
|
||||
return nil, fmt.Errorf("%w '%v' in enabled pairs", currency.ErrCurrencyNotFound, r.Currency)
|
||||
}
|
||||
|
||||
start := time.Now().AddDate(0, -1, 0)
|
||||
end := time.Now()
|
||||
if r.StartDate != "" {
|
||||
start, err = time.Parse(common.SimpleTimeFormat, r.StartDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.EndDate != "" {
|
||||
end, err = time.Parse(common.SimpleTimeFormat, r.EndDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = common.StartEndTimeCheck(start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &margin.RateHistoryRequest{
|
||||
Exchange: exch.GetName(),
|
||||
Asset: a,
|
||||
Currency: c,
|
||||
StartDate: start,
|
||||
EndDate: end,
|
||||
GetPredictedRate: r.GetPredictedRate,
|
||||
GetLendingPayments: r.GetLendingPayments,
|
||||
GetBorrowRates: r.GetBorrowRates,
|
||||
GetBorrowCosts: r.GetBorrowCosts,
|
||||
CalculateOffline: r.CalculateOffline,
|
||||
}
|
||||
if req.CalculateOffline {
|
||||
if r.TakerFeeRate == "" {
|
||||
return nil, fmt.Errorf("%w for offline calculations", common.ErrCannotCalculateOffline)
|
||||
}
|
||||
req.TakeFeeRate, err = decimal.NewFromString(r.TakerFeeRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.TakeFeeRate.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("%w for offline calculations", common.ErrCannotCalculateOffline)
|
||||
}
|
||||
if len(r.Rates) == 0 {
|
||||
return nil, fmt.Errorf("%w for offline calculations", common.ErrCannotCalculateOffline)
|
||||
}
|
||||
req.Rates = make([]margin.Rate, len(r.Rates))
|
||||
for i := range r.Rates {
|
||||
var offlineRate margin.Rate
|
||||
offlineRate.Time, err = time.Parse(common.SimpleTimeFormat, r.Rates[i].Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offlineRate.HourlyRate, err = decimal.NewFromString(r.Rates[i].HourlyRate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.Rates[i].BorrowCost != nil {
|
||||
offlineRate.BorrowCost.Size, err = decimal.NewFromString(r.Rates[i].BorrowCost.Size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.Rates[i].LendingPayment != nil {
|
||||
offlineRate.LendingPayment.Size, err = decimal.NewFromString(r.Rates[i].LendingPayment.Size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req.Rates[i] = offlineRate
|
||||
}
|
||||
}
|
||||
|
||||
lendingResp, err := exch.GetMarginRatesHistory(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(lendingResp.Rates) == 0 {
|
||||
return nil, order.ErrNoRates
|
||||
}
|
||||
resp := &gctrpc.GetMarginRatesHistoryResponse{
|
||||
LatestRate: &gctrpc.MarginRate{
|
||||
Time: lendingResp.Rates[len(lendingResp.Rates)-1].Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
HourlyRate: lendingResp.Rates[len(lendingResp.Rates)-1].HourlyRate.String(),
|
||||
YearlyRate: lendingResp.Rates[len(lendingResp.Rates)-1].YearlyRate.String(),
|
||||
MarketBorrowSize: lendingResp.Rates[len(lendingResp.Rates)-1].MarketBorrowSize.String(),
|
||||
},
|
||||
TotalRates: int64(len(lendingResp.Rates)),
|
||||
}
|
||||
if r.GetBorrowRates {
|
||||
resp.LatestRate.HourlyBorrowRate = lendingResp.Rates[len(lendingResp.Rates)-1].HourlyBorrowRate.String()
|
||||
resp.LatestRate.YearlyBorrowRate = lendingResp.Rates[len(lendingResp.Rates)-1].YearlyBorrowRate.String()
|
||||
}
|
||||
if r.GetBorrowRates || r.GetLendingPayments {
|
||||
resp.TakerFeeRate = lendingResp.TakerFeeRate.String()
|
||||
}
|
||||
if r.GetLendingPayments {
|
||||
resp.SumLendingPayments = lendingResp.SumLendingPayments.String()
|
||||
resp.AvgLendingSize = lendingResp.AverageLendingSize.String()
|
||||
}
|
||||
if r.GetBorrowCosts {
|
||||
resp.SumBorrowCosts = lendingResp.SumBorrowCosts.String()
|
||||
resp.AvgBorrowSize = lendingResp.AverageBorrowSize.String()
|
||||
}
|
||||
if r.GetPredictedRate {
|
||||
resp.PredictedRate = &gctrpc.MarginRate{
|
||||
Time: lendingResp.PredictedRate.Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
HourlyRate: lendingResp.PredictedRate.HourlyRate.String(),
|
||||
YearlyRate: lendingResp.PredictedRate.YearlyRate.String(),
|
||||
}
|
||||
if r.GetBorrowRates {
|
||||
resp.PredictedRate.HourlyBorrowRate = lendingResp.PredictedRate.HourlyBorrowRate.String()
|
||||
resp.PredictedRate.YearlyBorrowRate = lendingResp.PredictedRate.YearlyBorrowRate.String()
|
||||
}
|
||||
}
|
||||
if r.IncludeAllRates {
|
||||
resp.Rates = make([]*gctrpc.MarginRate, len(lendingResp.Rates))
|
||||
for i := range lendingResp.Rates {
|
||||
rate := &gctrpc.MarginRate{
|
||||
Time: lendingResp.Rates[i].Time.Format(common.SimpleTimeFormatWithTimezone),
|
||||
HourlyRate: lendingResp.Rates[i].HourlyRate.String(),
|
||||
YearlyRate: lendingResp.Rates[i].YearlyRate.String(),
|
||||
MarketBorrowSize: lendingResp.Rates[i].MarketBorrowSize.String(),
|
||||
}
|
||||
if r.GetBorrowRates {
|
||||
rate.HourlyBorrowRate = lendingResp.Rates[i].HourlyBorrowRate.String()
|
||||
rate.YearlyBorrowRate = lendingResp.Rates[i].YearlyBorrowRate.String()
|
||||
}
|
||||
if r.GetBorrowCosts {
|
||||
rate.BorrowCost = &gctrpc.BorrowCost{
|
||||
Cost: lendingResp.Rates[i].BorrowCost.Cost.String(),
|
||||
Size: lendingResp.Rates[i].BorrowCost.Size.String(),
|
||||
}
|
||||
}
|
||||
if r.GetLendingPayments {
|
||||
rate.LendingPayment = &gctrpc.LendingPayment{
|
||||
Payment: lendingResp.Rates[i].LendingPayment.Payment.String(),
|
||||
Size: lendingResp.Rates[i].LendingPayment.Size.String(),
|
||||
}
|
||||
}
|
||||
resp.Rates[i] = rate
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/binance"
|
||||
"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/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/trade"
|
||||
@@ -98,6 +99,37 @@ func (f fExchange) GetHistoricCandlesExtended(ctx context.Context, p currency.Pa
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f fExchange) GetMarginRatesHistory(context.Context, *margin.RateHistoryRequest) (*margin.RateHistoryResponse, error) {
|
||||
leet := decimal.NewFromInt(1337)
|
||||
rates := []margin.Rate{
|
||||
{
|
||||
Time: time.Now(),
|
||||
MarketBorrowSize: leet,
|
||||
HourlyRate: leet,
|
||||
HourlyBorrowRate: leet,
|
||||
LendingPayment: margin.LendingPayment{
|
||||
Payment: leet,
|
||||
Size: leet,
|
||||
},
|
||||
BorrowCost: margin.BorrowCost{
|
||||
Cost: leet,
|
||||
Size: leet,
|
||||
},
|
||||
},
|
||||
}
|
||||
resp := &margin.RateHistoryResponse{
|
||||
Rates: rates,
|
||||
SumBorrowCosts: leet,
|
||||
AverageBorrowSize: leet,
|
||||
SumLendingPayments: leet,
|
||||
AverageLendingSize: leet,
|
||||
PredictedRate: rates[0],
|
||||
TakerFeeRate: leet,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (f fExchange) FetchTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) {
|
||||
return &ticker.Price{
|
||||
Last: 1337,
|
||||
@@ -2619,3 +2651,141 @@ func TestGetTechnicalAnalysis(t *testing.T) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", len(resp.Signals["RSI"].Signals), 33)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMarginRatesHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
em := SetupExchangeManager()
|
||||
exch, err := em.NewExchangeByName(testExchange)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b := exch.GetBase()
|
||||
b.Name = fakeExchangeName
|
||||
b.Enabled = true
|
||||
|
||||
cp, err := currency.NewPairFromString("btc-usd")
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received '%v', expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore)
|
||||
b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
Available: currency.Pairs{cp},
|
||||
Enabled: currency.Pairs{cp},
|
||||
}
|
||||
fakeExchange := fExchange{
|
||||
IBotExchange: exch,
|
||||
}
|
||||
em.Add(fakeExchange)
|
||||
s := RPCServer{
|
||||
Engine: &Engine{
|
||||
ExchangeManager: em,
|
||||
currencyStateManager: &CurrencyStateManager{
|
||||
started: 1, iExchangeManager: em,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), nil)
|
||||
if !errors.Is(err, common.ErrNilPointer) {
|
||||
t.Errorf("received '%v' expected '%v'", err, common.ErrNilPointer)
|
||||
}
|
||||
|
||||
request := &gctrpc.GetMarginRatesHistoryRequest{}
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, ErrExchangeNameIsEmpty) {
|
||||
t.Errorf("received '%v' expected '%v'", err, ErrExchangeNameIsEmpty)
|
||||
}
|
||||
|
||||
request.Exchange = fakeExchangeName
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, asset.ErrNotSupported) {
|
||||
t.Errorf("received '%v' expected '%v'", err, asset.ErrNotSupported)
|
||||
}
|
||||
|
||||
request.Asset = asset.Spot.String()
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, currency.ErrCurrencyNotFound) {
|
||||
t.Errorf("received '%v' expected '%v'", err, currency.ErrCurrencyNotFound)
|
||||
}
|
||||
|
||||
request.Currency = "usd"
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
request.GetBorrowRates = true
|
||||
request.GetLendingPayments = true
|
||||
request.GetBorrowCosts = true
|
||||
request.GetPredictedRate = true
|
||||
request.IncludeAllRates = true
|
||||
resp, err := s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expected '%v'", err, nil)
|
||||
}
|
||||
if len(resp.Rates) == 0 {
|
||||
t.Errorf("received '%v' expected '%v'", len(resp.Rates), 1)
|
||||
}
|
||||
if resp.PredictedRate == nil {
|
||||
t.Errorf("received '%v' expected '%v'", nil, "not nil")
|
||||
}
|
||||
if resp.TakerFeeRate != "1337" {
|
||||
t.Errorf("received '%v' expected '%v'", resp.TakerFeeRate, "1337")
|
||||
}
|
||||
if resp.SumLendingPayments != "1337" {
|
||||
t.Errorf("received '%v' expected '%v'", resp.SumLendingPayments, "1337")
|
||||
}
|
||||
if resp.AvgBorrowSize != "1337" {
|
||||
t.Errorf("received '%v' expected '%v'", resp.AvgBorrowSize, "1337")
|
||||
}
|
||||
if resp.AvgLendingSize != "1337" {
|
||||
t.Errorf("received '%v' expected '%v'", resp.AvgLendingSize, "1337")
|
||||
}
|
||||
if resp.SumBorrowCosts != "1337" {
|
||||
t.Errorf("received '%v' expected '%v'", resp.SumBorrowCosts, "1337")
|
||||
}
|
||||
|
||||
request.CalculateOffline = true
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, common.ErrCannotCalculateOffline) {
|
||||
t.Errorf("received '%v' expected '%v'", err, common.ErrCannotCalculateOffline)
|
||||
}
|
||||
|
||||
request.TakerFeeRate = "-1337"
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, common.ErrCannotCalculateOffline) {
|
||||
t.Errorf("received '%v' expected '%v'", err, common.ErrCannotCalculateOffline)
|
||||
}
|
||||
|
||||
request.TakerFeeRate = "1337"
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, common.ErrCannotCalculateOffline) {
|
||||
t.Errorf("received '%v' expected '%v'", err, common.ErrCannotCalculateOffline)
|
||||
}
|
||||
|
||||
request.Rates = []*gctrpc.MarginRate{
|
||||
{
|
||||
Time: time.Now().Format(common.SimpleTimeFormat),
|
||||
HourlyRate: "1337",
|
||||
},
|
||||
}
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expected '%v'", err, nil)
|
||||
}
|
||||
|
||||
request.Rates = []*gctrpc.MarginRate{
|
||||
{
|
||||
Time: time.Now().Format(common.SimpleTimeFormat),
|
||||
HourlyRate: "1337",
|
||||
LendingPayment: &gctrpc.LendingPayment{Size: "1337"},
|
||||
BorrowCost: &gctrpc.BorrowCost{Size: "1337"},
|
||||
},
|
||||
}
|
||||
_, err = s.GetMarginRatesHistory(context.Background(), request)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received '%v' expected '%v'", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user