diff --git a/client.go b/client.go index 4348617..ba68a50 100644 --- a/client.go +++ b/client.go @@ -32,7 +32,8 @@ type Client struct { key string secret string - checkResponseBody checkResponseBodyFunc + checkResponseBody checkResponseBodyFunc + syncTimeDeltaNanoSeconds int64 } // NewClient : @@ -399,7 +400,31 @@ func (c *Client) deletePrivately(path string, query url.Values, dst interface{}) } func (c *Client) getTimestamp() int64 { - now := time.Now() - unixNano := now.UnixNano() - return unixNano / 1000000 + return (time.Now().UnixNano() - c.syncTimeDeltaNanoSeconds) / 1000000 +} + +func (c *Client) updateSyncTimeDelta( + remoteServerTimeRaw string, + localTimestampNanoseconds int64, +) error { + remoteServerTimeNS, err := strconv.ParseInt(remoteServerTimeRaw, 10, 64) + if err != nil { + return fmt.Errorf("parse server time: %w", err) + } + + c.syncTimeDeltaNanoSeconds = localTimestampNanoseconds - remoteServerTimeNS + return nil +} + +func (c *Client) SyncServerTime() error { + r, err := c.NewTimeService().GetServerTime() + if err != nil { + return fmt.Errorf("get server time: %w", err) + } + + if r.RetMsg != "OK" { + return fmt.Errorf("get server time: %s", r.RetMsg) + } + + return c.updateSyncTimeDelta(r.Result.TimeNano, time.Now().UnixNano()) } diff --git a/client_service.go b/client_service.go index 8285d12..a68550f 100644 --- a/client_service.go +++ b/client_service.go @@ -26,6 +26,11 @@ func (c *Client) Spot() SpotServiceI { return &SpotService{c} } +// NewTimeService : +func (c *Client) NewTimeService() TimeServiceI { + return &TimeService{c} +} + // FutureServiceI : type FutureServiceI interface { InversePerpetual() FutureInversePerpetualServiceI diff --git a/time_service.go b/time_service.go new file mode 100644 index 0000000..c1832eb --- /dev/null +++ b/time_service.go @@ -0,0 +1,33 @@ +package bybit + +// TimeService : +type TimeService struct { + client *Client +} + +// TimeServiceI : +type TimeServiceI interface { + GetServerTime() (*GetServerTimeResponse, error) +} + +// GetServerTimeResponse : +type GetServerTimeResponse struct { + CommonResponse `json:",inline"` + Result GetServerTimeResult `json:"result"` +} + +type GetServerTimeResult struct { + TimeSecond string `json:"timeSecond"` + TimeNano string `json:"timeNano"` +} + +// GetServerTime : +func (s *TimeService) GetServerTime() (*GetServerTimeResponse, error) { + var res GetServerTimeResponse + + if err := s.client.getPublicly("/v3/public/time", nil, &res); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/time_service_test.go b/time_service_test.go new file mode 100644 index 0000000..65bd098 --- /dev/null +++ b/time_service_test.go @@ -0,0 +1,30 @@ +package bybit + +import ( + "math" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUpdateSyncTimeDelta(t *testing.T) { + // given + c := &Client{} + remoteServerTimeRaw := "1688721231460000000" + localTimestampNanoseconds := int64(1688721231560000000) + expectedNsDelta := int64(100000000) + recvWindowMs := int64(5000) + nowTimestampMs := time.Now().UnixMilli() + + // when + err := c.updateSyncTimeDelta(remoteServerTimeRaw, localTimestampNanoseconds) + + // then + require.NoError(t, err) + assert.Equal(t, expectedNsDelta, c.syncTimeDeltaNanoSeconds) + + timestampMsDelta := int64(math.Abs(float64(c.getTimestamp()) - float64(nowTimestampMs))) + assert.Less(t, timestampMsDelta, recvWindowMs) +}