Skip to content

Commit 22bbe08

Browse files
authored
Merge pull request #680 from pcriadoperez/portfolio
feat: Porfolio Margin API support
2 parents 3a51af6 + 4f0a8e3 commit 22bbe08

File tree

317 files changed

+24185
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

317 files changed

+24185
-0
lines changed

.github/workflows/check.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ jobs:
3434
run: ./check.sh vet
3535
- name: UnitTest
3636
run: ./check.sh unittest
37+
# - name: IntegrationTest
38+
# run: ./check.sh integration

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Name | Description | Status
3131
[futures-api.md](https://binance-docs.github.io/apidocs/futures/en/#general-info) | Details on the Futures API (/fapi) | <input type="checkbox" checked> Implemented
3232
[delivery-api.md](https://binance-docs.github.io/apidocs/delivery/en/#general-info) | Details on the Coin-M Futures API (/dapi) | <input type="checkbox" checked> Implemented
3333
[options-api.md](https://binance-docs.github.io/apidocs/voptions/en/#general-info) | Details on the Options API(/eapi) | <input type="checkbox" checked> Implemented
34+
[portfolio-margin-api.md](https://developers.binance.com/docs/derivatives/portfolio-margin/general-info) | Details on the Portfolio Margin API(/papi) | <input type="checkbox" checked> Implemented
3435

3536

3637
If you find an unimplemented interface, please submit an issue. It's great if you can open a PR to fix it.

check.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ function unittest() {
3737
)
3838
}
3939

40+
function integration() {
41+
echo "Running integration test ..."
42+
cd v2
43+
go test -v -tags=integration -race -coverprofile=coverage.txt -covermode=atomic ./...
44+
}
45+
4046
if [[ -z $ACTION ]]; then
4147
format
4248
# lint

typos.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ extend-ignore-re=[
1919
"[Cc]ummulative",
2020
"OTU",
2121
"[Tt]ransfered",
22+
"DELIVERED_SETTELMENT",
23+
"SETTELMENT",
24+
"[Aa]ccured"
2225
]
2326
check-filename = true
2427

v2/portfolio/account_service.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package portfolio
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
9+
// GetAccountService get account information
10+
type GetAccountService struct {
11+
c *Client
12+
}
13+
14+
// Do send request
15+
func (s *GetAccountService) Do(ctx context.Context, opts ...RequestOption) (res *Account, err error) {
16+
r := &request{
17+
method: http.MethodGet,
18+
endpoint: "/papi/v1/account",
19+
secType: secTypeSigned,
20+
}
21+
data, _, err := s.c.callAPI(ctx, r, opts...)
22+
if err != nil {
23+
return nil, err
24+
}
25+
res = new(Account)
26+
err = json.Unmarshal(data, res)
27+
if err != nil {
28+
return nil, err
29+
}
30+
return res, nil
31+
}
32+
33+
// Account define account info
34+
type Account struct {
35+
UniMMR string `json:"uniMMR"` // Portfolio margin account maintenance margin rate
36+
AccountEquity string `json:"accountEquity"` // Account equity, in USD value
37+
ActualEquity string `json:"actualEquity"` // Account equity without collateral rate, in USD value
38+
AccountInitialMargin string `json:"accountInitialMargin"`
39+
AccountMaintMargin string `json:"accountMaintMargin"` // Portfolio margin account maintenance margin, unit:USD
40+
AccountStatus string `json:"accountStatus"` // Portfolio margin account status
41+
VirtualMaxWithdrawAmount string `json:"virtualMaxWithdrawAmount"` // Portfolio margin maximum amount for transfer out in USD
42+
TotalAvailableBalance string `json:"totalAvailableBalance"`
43+
TotalMarginOpenLoss string `json:"totalMarginOpenLoss"` // in USD margin open order
44+
UpdateTime int64 `json:"updateTime"` // last update time
45+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package portfolio
5+
6+
import (
7+
"context"
8+
"testing"
9+
)
10+
11+
type accountServiceIntegrationTestSuite struct {
12+
*baseIntegrationTestSuite
13+
}
14+
15+
func TestAccountServiceIntegration(t *testing.T) {
16+
base := SetupTest(t)
17+
suite := &accountServiceIntegrationTestSuite{
18+
baseIntegrationTestSuite: base,
19+
}
20+
21+
t.Run("GetAccount", func(t *testing.T) {
22+
service := &GetAccountService{c: suite.client}
23+
account, err := service.Do(context.Background())
24+
if err != nil {
25+
t.Fatalf("Failed to get account info: %v", err)
26+
}
27+
28+
// Basic validation of returned data
29+
if account.AccountStatus == "" {
30+
t.Error("Expected non-empty account status")
31+
}
32+
33+
if account.AccountEquity == "" {
34+
t.Error("Expected non-empty account equity")
35+
}
36+
37+
if account.UniMMR == "" {
38+
t.Error("Expected non-empty uniMMR")
39+
}
40+
41+
if account.UpdateTime == 0 {
42+
t.Error("Expected non-zero update time")
43+
}
44+
})
45+
}

v2/portfolio/account_service_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package portfolio
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
)
8+
9+
type accountServiceTestSuite struct {
10+
baseTestSuite
11+
}
12+
13+
func TestAccountService(t *testing.T) {
14+
suite.Run(t, new(accountServiceTestSuite))
15+
}
16+
17+
func (s *accountServiceTestSuite) TestGetAccount() {
18+
data := []byte(`{
19+
"uniMMR": "5167.92171923",
20+
"accountEquity": "122607.35137903",
21+
"actualEquity": "73.47428058",
22+
"accountInitialMargin": "23.72469206",
23+
"accountMaintMargin": "23.72469206",
24+
"accountStatus": "NORMAL",
25+
"virtualMaxWithdrawAmount": "1627523.32459208",
26+
"totalAvailableBalance": "100.00",
27+
"totalMarginOpenLoss": "0.00",
28+
"updateTime": 1657707212154
29+
}`)
30+
s.mockDo(data, nil)
31+
defer s.assertDo()
32+
s.assertReq(func(r *request) {
33+
e := newSignedRequest()
34+
s.assertRequestEqual(e, r)
35+
})
36+
37+
res, err := s.client.NewGetAccountService().Do(newContext())
38+
s.r().NoError(err)
39+
s.assertAccountEqual(res, &Account{
40+
UniMMR: "5167.92171923",
41+
AccountEquity: "122607.35137903",
42+
ActualEquity: "73.47428058",
43+
AccountInitialMargin: "23.72469206",
44+
AccountMaintMargin: "23.72469206",
45+
AccountStatus: "NORMAL",
46+
VirtualMaxWithdrawAmount: "1627523.32459208",
47+
TotalAvailableBalance: "100.00",
48+
TotalMarginOpenLoss: "0.00",
49+
UpdateTime: 1657707212154,
50+
})
51+
}
52+
53+
func (s *accountServiceTestSuite) assertAccountEqual(a, e *Account) {
54+
r := s.r()
55+
r.Equal(e.UniMMR, a.UniMMR, "UniMMR")
56+
r.Equal(e.AccountEquity, a.AccountEquity, "AccountEquity")
57+
r.Equal(e.ActualEquity, a.ActualEquity, "ActualEquity")
58+
r.Equal(e.AccountInitialMargin, a.AccountInitialMargin, "AccountInitialMargin")
59+
r.Equal(e.AccountMaintMargin, a.AccountMaintMargin, "AccountMaintMargin")
60+
r.Equal(e.AccountStatus, a.AccountStatus, "AccountStatus")
61+
r.Equal(e.VirtualMaxWithdrawAmount, a.VirtualMaxWithdrawAmount, "VirtualMaxWithdrawAmount")
62+
r.Equal(e.TotalAvailableBalance, a.TotalAvailableBalance, "TotalAvailableBalance")
63+
r.Equal(e.TotalMarginOpenLoss, a.TotalMarginOpenLoss, "TotalMarginOpenLoss")
64+
r.Equal(e.UpdateTime, a.UpdateTime, "UpdateTime")
65+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package portfolio
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
9+
// GetAutoRepayFuturesStatusService get auto-repay-futures status
10+
type GetAutoRepayFuturesStatusService struct {
11+
c *Client
12+
}
13+
14+
// Do send request
15+
func (s *GetAutoRepayFuturesStatusService) Do(ctx context.Context, opts ...RequestOption) (*AutoRepayFuturesStatus, error) {
16+
r := &request{
17+
method: http.MethodGet,
18+
endpoint: "/papi/v1/repay-futures-switch",
19+
secType: secTypeSigned,
20+
}
21+
22+
data, _, err := s.c.callAPI(ctx, r, opts...)
23+
if err != nil {
24+
return nil, err
25+
}
26+
res := new(AutoRepayFuturesStatus)
27+
err = json.Unmarshal(data, res)
28+
if err != nil {
29+
return nil, err
30+
}
31+
return res, nil
32+
}
33+
34+
// AutoRepayFuturesStatus define auto repay futures status
35+
type AutoRepayFuturesStatus struct {
36+
AutoRepay bool `json:"autoRepay"` // true for turn on the auto-repay futures; false for turn off
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package portfolio
5+
6+
import (
7+
"context"
8+
"testing"
9+
)
10+
11+
type autoRepayFuturesStatusServiceIntegrationTestSuite struct {
12+
*baseIntegrationTestSuite
13+
}
14+
15+
func TestAutoRepayFuturesStatusServiceIntegration(t *testing.T) {
16+
base := SetupTest(t)
17+
suite := &autoRepayFuturesStatusServiceIntegrationTestSuite{
18+
baseIntegrationTestSuite: base,
19+
}
20+
21+
t.Run("GetAutoRepayFuturesStatus", func(t *testing.T) {
22+
service := &GetAutoRepayFuturesStatusService{c: suite.client}
23+
status, err := service.Do(context.Background())
24+
if err != nil {
25+
t.Fatalf("Failed to get auto repay futures status: %v", err)
26+
}
27+
28+
t.Logf("Auto repay futures is %v", status.AutoRepay)
29+
})
30+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package portfolio
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
)
8+
9+
type autoRepayFuturesStatusServiceTestSuite struct {
10+
baseTestSuite
11+
}
12+
13+
func TestAutoRepayFuturesStatusService(t *testing.T) {
14+
suite.Run(t, new(autoRepayFuturesStatusServiceTestSuite))
15+
}
16+
17+
func (s *autoRepayFuturesStatusServiceTestSuite) TestGetAutoRepayFuturesStatus() {
18+
data := []byte(`{
19+
"autoRepay": true
20+
}`)
21+
s.mockDo(data, nil)
22+
defer s.assertDo()
23+
24+
s.assertReq(func(r *request) {
25+
e := newSignedRequest()
26+
s.assertRequestEqual(e, r)
27+
})
28+
29+
status, err := s.client.NewGetAutoRepayFuturesStatusService().Do(newContext())
30+
s.r().NoError(err)
31+
s.r().True(status.AutoRepay)
32+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package portfolio
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
)
8+
9+
// ChangeAutoRepayFuturesStatusService change auto-repay-futures status
10+
type ChangeAutoRepayFuturesStatusService struct {
11+
c *Client
12+
autoRepay bool
13+
}
14+
15+
// AutoRepay set auto repay status
16+
func (s *ChangeAutoRepayFuturesStatusService) AutoRepay(autoRepay bool) *ChangeAutoRepayFuturesStatusService {
17+
s.autoRepay = autoRepay
18+
return s
19+
}
20+
21+
// Do send request
22+
func (s *ChangeAutoRepayFuturesStatusService) Do(ctx context.Context, opts ...RequestOption) (*SuccessResponse, error) {
23+
r := &request{
24+
method: http.MethodPost,
25+
endpoint: "/papi/v1/repay-futures-switch",
26+
secType: secTypeSigned,
27+
}
28+
r.setParam("autoRepay", s.autoRepay)
29+
30+
data, _, err := s.c.callAPI(ctx, r, opts...)
31+
if err != nil {
32+
return nil, err
33+
}
34+
res := new(SuccessResponse)
35+
err = json.Unmarshal(data, res)
36+
if err != nil {
37+
return nil, err
38+
}
39+
return res, nil
40+
}
41+
42+
// SuccessResponse define success response
43+
type SuccessResponse struct {
44+
Msg string `json:"msg"`
45+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//go:build integration
2+
// +build integration
3+
4+
package portfolio
5+
6+
import (
7+
"context"
8+
"testing"
9+
)
10+
11+
type autoRepayFuturesSwitchServiceIntegrationTestSuite struct {
12+
*baseIntegrationTestSuite
13+
}
14+
15+
func TestAutoRepayFuturesSwitchServiceIntegration(t *testing.T) {
16+
base := SetupTest(t)
17+
suite := &autoRepayFuturesSwitchServiceIntegrationTestSuite{
18+
baseIntegrationTestSuite: base,
19+
}
20+
21+
t.Run("ChangeAutoRepayFuturesStatus", func(t *testing.T) {
22+
service := &ChangeAutoRepayFuturesStatusService{c: suite.client}
23+
24+
// Get current status
25+
currentStatus, err := suite.client.NewGetAutoRepayFuturesStatusService().Do(context.Background())
26+
if err != nil {
27+
t.Fatalf("Failed to get current auto repay futures status: %v", err)
28+
}
29+
30+
// Change status
31+
newStatus := !currentStatus.AutoRepay
32+
res, err := service.AutoRepay(newStatus).Do(context.Background())
33+
if err != nil {
34+
t.Fatalf("Failed to change auto repay futures status: %v", err)
35+
}
36+
37+
if res.Msg != "success" {
38+
t.Errorf("Expected success message, got %v", res.Msg)
39+
}
40+
41+
// Revert to original status
42+
_, err = service.AutoRepay(currentStatus.AutoRepay).Do(context.Background())
43+
if err != nil {
44+
t.Fatalf("Failed to revert auto repay futures status: %v", err)
45+
}
46+
})
47+
}

0 commit comments

Comments
 (0)