Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Futures order position tracking & FTX scaled collateral calculation #868

Merged
merged 49 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a22be78
implements futures functions and GRPC functions on new branch
gloriousCode Jan 6, 2022
44d1780
lint and test fixes
gloriousCode Jan 7, 2022
4f19acc
Fix uneven split pnl. Adds collateral weight test. docs. New clear func
gloriousCode Jan 7, 2022
10cedd3
Test protection if someone has zero collateral
gloriousCode Jan 7, 2022
bc67a86
Uses string instead of double for accuracy
gloriousCode Jan 9, 2022
025452c
Fixes old code panic
gloriousCode Jan 9, 2022
d8bb688
context, match, docs
gloriousCode Jan 10, 2022
ac02d53
Addresses Shazniterinos, var names, expanded tests
gloriousCode Jan 11, 2022
bac9ce7
Returns subaccount name, provides USD values when offlinecalc
gloriousCode Jan 13, 2022
665fcc9
Fixes oopsie
gloriousCode Jan 13, 2022
406d3c2
Fixes cool bug which allowed made up subaccount results
gloriousCode Jan 13, 2022
8f76c78
Subaccount override on FTX, subaccount results for collateral
gloriousCode Jan 14, 2022
f276cff
Strenghten collateral account info checks. Improve FTX test
gloriousCode Jan 17, 2022
1d0d9e0
English is my first language
gloriousCode Jan 17, 2022
13b4878
Fixes oopsies
gloriousCode Jan 17, 2022
eb3bbb3
Merge branch 'master' into future-grpc
gloriousCode Jan 19, 2022
406f26c
Fixes for unrealised PNL & collateral rendering
gloriousCode Jan 28, 2022
b4c0ae8
Fixes lint and tests
gloriousCode Jan 28, 2022
df3bbf5
Shaznit fixes
gloriousCode Jan 31, 2022
295ac32
Secret Shaznit
gloriousCode Jan 31, 2022
406c485
Updates account information across wrappers to include more fields
gloriousCode Feb 1, 2022
59cfc0c
Updates online collateral calculations. Updates RPC data
gloriousCode Feb 2, 2022
50bd9e5
Accurately calculates collateral offline and online minus testing
gloriousCode Feb 2, 2022
9f4c443
Tests and lint chocolate
gloriousCode Feb 3, 2022
bc083af
Simplifies accountinfo results
gloriousCode Feb 3, 2022
ce34353
Fixes shaznits
gloriousCode Feb 3, 2022
d4e5b3a
Merge branch 'master' into future-grpc
gloriousCode Feb 17, 2022
7cb62cb
Adds new func
gloriousCode Feb 17, 2022
85d78ff
Increases collateral accuracy again again again x 200
gloriousCode Feb 18, 2022
45f4585
Increases accuracy of collateral rendering
gloriousCode Feb 21, 2022
3da4017
Merge branch 'master' into future-grpc
gloriousCode Feb 21, 2022
c6a951f
Fixes minor merge/test issues
gloriousCode Feb 21, 2022
1180e96
Linterino
gloriousCode Feb 21, 2022
84ad15e
Fixes ws test. Improves collateral calculations and rendering
gloriousCode Feb 23, 2022
8209696
Make it prettier
gloriousCode Feb 23, 2022
f692f1c
Removes the lock I put on :eyes:
gloriousCode Feb 23, 2022
16e145f
Adds `additional_collateral_used` field, renders orig currency
gloriousCode Feb 25, 2022
0d4b413
Fixes unrelated test
gloriousCode Feb 25, 2022
948b680
Fix test
gloriousCode Feb 25, 2022
8a8e173
Correctly calculate spot margin borrow collateral
gloriousCode Feb 25, 2022
9dd2a8d
Address fun lint surprise
gloriousCode Feb 28, 2022
c109172
Strange lint fixing x2
gloriousCode Feb 28, 2022
b0b7cb5
Continued lint journey
gloriousCode Feb 28, 2022
a27effb
Nolint the nolint to not lint the lint
gloriousCode Feb 28, 2022
bc6d6a0
Adds two new fields to response
gloriousCode Feb 28, 2022
94fbd40
More linting issues arising
gloriousCode Feb 28, 2022
3f5fff3
fIX3s_c4s|NG
gloriousCode Feb 28, 2022
dd5e5a2
Fixes command flags' incorrect numbering
gloriousCode Feb 28, 2022
bd73beb
FairMarket = Won
gloriousCode Feb 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/gctcli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -4941,7 +4941,7 @@ var getCollateralCommand = &cli.Command{
&cli.BoolFlag{
Name: "calculateoffline",
Aliases: []string{"c"},
Usage: "use local scaling methods instead of requesting additional API information, depending on individual exchange support",
Usage: "use local scaling calculations instead of requesting the collateral values directly, depending on individual exchange support",
},
&cli.BoolFlag{
Name: "includebreakdown",
Expand Down
56 changes: 44 additions & 12 deletions engine/rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var (
errCurrencyPairInvalid = errors.New("currency provided is not found in the available pairs list")
errNoTrades = errors.New("no trades returned from supplied params")
errNilRequestData = errors.New("nil request data received, cannot continue")
errNoAccountInformation = errors.New("account information does not exist")
)

// RPCServer struct
Expand Down Expand Up @@ -4125,6 +4126,9 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
if err != nil {
return nil, err
}
if !a.IsFutures() {
return nil, fmt.Errorf("%s %w", a, order.ErrNotFuturesAsset)
}
var start, end time.Time
if r.StartDate != "" {
start, err = time.Parse(common.SimpleTimeFormat, r.StartDate)
Expand Down Expand Up @@ -4168,7 +4172,10 @@ func (s *RPCServer) GetFuturesPositions(ctx context.Context, r *gctrpc.GetFuture
if err != nil {
return nil, err
}
response := &gctrpc.GetFuturesPositionsResponse{}
b := exch.GetBase()
response := &gctrpc.GetFuturesPositionsResponse{
SubAccount: b.API.Credentials.Subaccount,
}
var totalRealisedPNL, totalUnrealisedPNL decimal.Decimal
for i := range pos {
if r.PositionLimit > 0 && len(response.Positions) >= int(r.PositionLimit) {
Expand Down Expand Up @@ -4264,50 +4271,75 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe
if err != nil {
return nil, err
}
if !a.IsFutures() {
return nil, fmt.Errorf("%s %w", a, order.ErrNotFuturesAsset)
}
ai, err := exch.FetchAccountInfo(ctx, a)
if err != nil {
return nil, err
}

var calculators []order.CollateralCalculator
var acc account.SubAccount
var acc *account.SubAccount
if r.SubAccount != "" {
for i := range ai.Accounts {
if strings.EqualFold(r.SubAccount, ai.Accounts[i].ID) {
acc = ai.Accounts[i]
acc = &ai.Accounts[i]
break
}
}
} else if len(ai.Accounts) > 0 {
acc = ai.Accounts[0]
acc = &ai.Accounts[0]
shazbert marked this conversation as resolved.
Show resolved Hide resolved
}
if acc == nil {
return nil, fmt.Errorf("%w for %s %s and stored credentials", errNoAccountInformation, exch.GetName(), r.SubAccount)
}
for i := range acc.Currencies {
if acc.Currencies[i].TotalValue == 0 {
continue
}
calculators = append(calculators, order.CollateralCalculator{
cal := order.CollateralCalculator{
CalculateOffline: r.CalculateOffline,
CollateralCurrency: acc.Currencies[i].CurrencyName,
Asset: a,
CollateralAmount: decimal.NewFromFloat(acc.Currencies[i].TotalValue),
})
}
if r.CalculateOffline && !acc.Currencies[i].CurrencyName.Match(currency.USD) {
var tick *ticker.Price
tick, err = exch.FetchTicker(ctx, currency.NewPair(acc.Currencies[i].CurrencyName, currency.USD), asset.Spot)
if err != nil {
log.Errorf(log.GRPCSys, fmt.Sprintf("GetCollateral offline calculation via FetchTicker %s %s", exch.GetName(), err))
continue
}
if tick.Last == 0 {
continue
}
cal.USDPrice = decimal.NewFromFloat(tick.Last)
}
calculators = append(calculators, cal)
}

collateral, err := exch.CalculateTotalCollateral(ctx, calculators)
if err != nil {
return nil, err
}
subAccount := r.SubAccount
if subAccount == "" {
b := exch.GetBase()
subAccount = b.API.Credentials.Subaccount
}

result := &gctrpc.GetCollateralResponse{
SubAccount: subAccount,
TotalCollateral: collateral.TotalCollateral.String(),
}
if r.IncludeBreakdown {
for i := range collateral.BreakdownByCurrency {
result.CurrencyBreakdown = append(result.CurrencyBreakdown, &gctrpc.CollateralForCurrency{
cb := &gctrpc.CollateralForCurrency{
Currency: collateral.BreakdownByCurrency[i].Currency.String(),
ScaledCollateral: collateral.BreakdownByCurrency[i].Amount.String(),
ScaledToCurrency: collateral.BreakdownByCurrency[i].ValueCurrency.String(),
})
}
if collateral.BreakdownByCurrency[i].Error != nil {
cb.Error = collateral.BreakdownByCurrency[i].Error.Error()
}
result.CurrencyBreakdown = append(result.CurrencyBreakdown, cb)
}
}
return result, nil
Expand Down
40 changes: 38 additions & 2 deletions engine/rpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2049,6 +2049,12 @@ func TestGetFuturesPositions(t *testing.T) {
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
fakeExchange := fExchange{
IBotExchange: exch,
}
Expand Down Expand Up @@ -2079,8 +2085,8 @@ func TestGetFuturesPositions(t *testing.T) {
},
Verbose: true,
})
if err != nil {
t.Error(err)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}
if r == nil {
t.Fatal("expected not nil response")
Expand All @@ -2091,6 +2097,20 @@ func TestGetFuturesPositions(t *testing.T) {
if r.TotalOrders != 1 {
t.Fatal("expected 1 order")
}

_, err = s.GetFuturesPositions(context.Background(), &gctrpc.GetFuturesPositionsRequest{
Exchange: fakeExchangeName,
Asset: asset.Spot.String(),
Pair: &gctrpc.CurrencyPair{
Delimiter: currency.DashDelimiter,
Base: cp.Base.String(),
Quote: cp.Quote.String(),
},
Verbose: true,
})
if !errors.Is(err, order.ErrNotFuturesAsset) {
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
}
}

func TestGetCollateral(t *testing.T) {
Expand All @@ -2116,6 +2136,12 @@ func TestGetCollateral(t *testing.T) {
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
b.CurrencyPairs.Pairs[asset.Spot] = &currency.PairStore{
AssetEnabled: convert.BoolPtr(true),
ConfigFormat: &currency.PairFormat{},
Available: currency.Pairs{cp},
Enabled: currency.Pairs{cp},
}
fakeExchange := fExchange{
IBotExchange: exch,
}
Expand Down Expand Up @@ -2155,4 +2181,14 @@ func TestGetCollateral(t *testing.T) {
if r.TotalCollateral != "1337" {
t.Error("expected 1337")
}

_, err = s.GetCollateral(context.Background(), &gctrpc.GetCollateralRequest{
Exchange: fakeExchangeName,
Asset: asset.Spot.String(),
IncludeBreakdown: true,
SubAccount: "1337",
})
if !errors.Is(err, order.ErrNotFuturesAsset) {
t.Errorf("received '%v', expected '%v'", err, order.ErrNotFuturesAsset)
}
}
31 changes: 31 additions & 0 deletions exchanges/ftx/ftx.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const (
getHistoricalData = "/markets/%s/candles"
getFutures = "/futures"
getFuture = "/futures/"
getExpiredFutures = "/expired_futures"
getFutureStats = "/futures/%s/stats"
getFundingRates = "/funding_rates"
getIndexWeights = "/indexes/%s/weights"
Expand Down Expand Up @@ -309,6 +310,36 @@ func (f *FTX) GetFutureStats(ctx context.Context, futureName string) (FutureStat
return resp.Data, f.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getFutureStats, futureName), &resp)
}

// GetExpiredFuture returns information on an expired futures contract
func (f *FTX) GetExpiredFuture(ctx context.Context, pair currency.Pair) (FuturesData, error) {
resp := struct {
Data []FuturesData `json:"result"`
}{}

p, err := f.FormatSymbol(pair, asset.Futures)
if err != nil {
return FuturesData{}, err
}
err = f.SendHTTPRequest(ctx, exchange.RestSpot, getExpiredFutures, &resp)
if err != nil {
return FuturesData{}, err
}
shazbert marked this conversation as resolved.
Show resolved Hide resolved
for i := range resp.Data {
if resp.Data[i].Name == p {
return resp.Data[i], nil
}
}
return FuturesData{}, fmt.Errorf("%s %s %w", f.Name, p, currency.ErrPairNotFound)
}

// GetExpiredFutures returns information on expired futures contracts
func (f *FTX) GetExpiredFutures(ctx context.Context) ([]FuturesData, error) {
resp := struct {
Data []FuturesData `json:"result"`
}{}
return resp.Data, f.SendHTTPRequest(ctx, exchange.RestSpot, getExpiredFutures, &resp)
}

// GetFundingRates gets data on funding rates
func (f *FTX) GetFundingRates(ctx context.Context, startTime, endTime time.Time, future string) ([]FundingRatesData, error) {
resp := struct {
Expand Down
32 changes: 31 additions & 1 deletion exchanges/ftx/ftx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1774,7 +1774,8 @@ func TestScaleCollateral(t *testing.T) {
CalculateOffline: true,
})
if err != nil {
if errors.Is(err, errCollateralCurrencyNotFound) {
if errors.Is(err, errCollateralCurrencyNotFound) ||
errors.Is(err, order.ErrUSDValueRequired) {
continue
}
t.Error(err)
Expand All @@ -1796,6 +1797,19 @@ func TestScaleCollateral(t *testing.T) {
}
liquidationScaling += scaled.InexactFloat64()

_, err = f.ScaleCollateral(context.Background(), &order.CollateralCalculator{
CollateralCurrency: currency.NewCode(coin),
Asset: asset.Spot,
Side: order.Buy,
CollateralAmount: decimal.NewFromFloat(v[v2].Total),
USDPrice: decimal.Zero,
IsLiquidating: true,
CalculateOffline: true,
})
if !errors.Is(err, order.ErrUSDValueRequired) {
t.Errorf("received '%v' exepected '%v'", err, order.ErrUSDValueRequired)
}

_, err = f.ScaleCollateral(context.Background(), &order.CollateralCalculator{
CollateralCurrency: currency.NewCode(coin),
Asset: asset.Spot,
Expand Down Expand Up @@ -2017,3 +2031,19 @@ func TestCollateralWeightHasData(t *testing.T) {
t.Error("expected true")
}
}

func TestGetExpiredFutures(t *testing.T) {
t.Parallel()
_, err := f.GetExpiredFutures(context.Background())
if err != nil {
t.Error(err)
}
}

func TestGetExpiredFuture(t *testing.T) {
t.Parallel()
_, err := f.GetExpiredFuture(context.Background(), currency.NewPairWithDelimiter("BTC", "1231", "-"))
if err != nil {
t.Error(err)
}
}
Loading