mirror of
https://github.com/c9s/bbgo.git
synced 2024-09-20 08:11:08 +00:00
Merge pull request #1678 from c9s/c9s/xalign/withdraw-detection
FEATURE: [xalign] add withdraw detection
This commit is contained in:
commit
eb317da21a
|
@ -8,8 +8,9 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=TransferType
|
||||
// 1 for internal transfer, 0 for external transfer
|
||||
//
|
||||
//go:generate stringer -type=TransferType
|
||||
type TransferType int
|
||||
|
||||
const (
|
||||
|
@ -33,7 +34,7 @@ type WithdrawRecord struct {
|
|||
TxID string `json:"txId"`
|
||||
}
|
||||
|
||||
//go:generate stringer -type=WithdrawStatus
|
||||
//go:generate stringer -type=WithdrawStatus -trimprefix=WithdrawStatus
|
||||
type WithdrawStatus int
|
||||
|
||||
// WithdrawStatus: 0(0:Email Sent,1:Cancelled 2:Awaiting Approval 3:Rejected 4:Processing 5:Failure 6:Completed)
|
||||
|
|
|
@ -212,6 +212,12 @@ func (g *GetWithdrawHistoryRequest) GetSlugsMap() (map[string]string, error) {
|
|||
return slugs, nil
|
||||
}
|
||||
|
||||
// GetPath returns the request path of the API
|
||||
func (g *GetWithdrawHistoryRequest) GetPath() string {
|
||||
return "/sapi/v1/capital/withdraw/history"
|
||||
}
|
||||
|
||||
// Do generates the request object and send the request object to the API endpoint
|
||||
func (g *GetWithdrawHistoryRequest) Do(ctx context.Context) ([]WithdrawRecord, error) {
|
||||
|
||||
// empty params for GET operation
|
||||
|
@ -221,7 +227,9 @@ func (g *GetWithdrawHistoryRequest) Do(ctx context.Context) ([]WithdrawRecord, e
|
|||
return nil, err
|
||||
}
|
||||
|
||||
apiURL := "/sapi/v1/capital/withdraw/history"
|
||||
var apiURL string
|
||||
|
||||
apiURL = g.GetPath()
|
||||
|
||||
req, err := g.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
|
||||
if err != nil {
|
||||
|
@ -234,8 +242,32 @@ func (g *GetWithdrawHistoryRequest) Do(ctx context.Context) ([]WithdrawRecord, e
|
|||
}
|
||||
|
||||
var apiResponse []WithdrawRecord
|
||||
|
||||
type responseUnmarshaler interface {
|
||||
Unmarshal(data []byte) error
|
||||
}
|
||||
|
||||
if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok {
|
||||
if err := unmarshaler.Unmarshal(response.Body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// The line below checks the content type, however, some API server might not send the correct content type header,
|
||||
// Hence, this is commented for backward compatibility
|
||||
// response.IsJSON()
|
||||
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
type responseValidator interface {
|
||||
Validate() error
|
||||
}
|
||||
|
||||
if validator, ok := interface{}(&apiResponse).(responseValidator); ok {
|
||||
if err := validator.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return apiResponse, nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by "stringer -type=WithdrawStatus"; DO NOT EDIT.
|
||||
// Code generated by "stringer -type=WithdrawStatus -trimprefix=WithdrawStatus"; DO NOT EDIT.
|
||||
|
||||
package binanceapi
|
||||
|
||||
|
@ -17,9 +17,9 @@ func _() {
|
|||
_ = x[WithdrawStatusCompleted-6]
|
||||
}
|
||||
|
||||
const _WithdrawStatus_name = "WithdrawStatusEmailSentWithdrawStatusCancelledWithdrawStatusAwaitingApprovalWithdrawStatusRejectedWithdrawStatusProcessingWithdrawStatusFailureWithdrawStatusCompleted"
|
||||
const _WithdrawStatus_name = "EmailSentCancelledAwaitingApprovalRejectedProcessingFailureCompleted"
|
||||
|
||||
var _WithdrawStatus_index = [...]uint8{0, 23, 46, 76, 98, 122, 143, 166}
|
||||
var _WithdrawStatus_index = [...]uint8{0, 9, 18, 34, 42, 52, 59, 68}
|
||||
|
||||
func (i WithdrawStatus) String() string {
|
||||
if i < 0 || i >= WithdrawStatus(len(_WithdrawStatus_index)-1) {
|
||||
|
|
|
@ -9,10 +9,32 @@ import (
|
|||
"github.com/adshao/go-binance/v2/futures"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/binance/binanceapi"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func toGlobalWithdrawStatus(status binanceapi.WithdrawStatus) (types.WithdrawStatus, error) {
|
||||
switch status {
|
||||
case binanceapi.WithdrawStatusEmailSent:
|
||||
return types.WithdrawStatusSent, nil
|
||||
case binanceapi.WithdrawStatusCancelled:
|
||||
return types.WithdrawStatusCancelled, nil
|
||||
case binanceapi.WithdrawStatusAwaitingApproval:
|
||||
return types.WithdrawStatusAwaitingApproval, nil
|
||||
case binanceapi.WithdrawStatusRejected:
|
||||
return types.WithdrawStatusRejected, nil
|
||||
case binanceapi.WithdrawStatusProcessing:
|
||||
return types.WithdrawStatusProcessing, nil
|
||||
case binanceapi.WithdrawStatusFailure:
|
||||
return types.WithdrawStatusFailed, nil
|
||||
case binanceapi.WithdrawStatusCompleted:
|
||||
return types.WithdrawStatusCompleted, nil
|
||||
default:
|
||||
return types.WithdrawStatusUnknown, fmt.Errorf("unable to convert the withdraw status: %s", status)
|
||||
}
|
||||
}
|
||||
|
||||
func toGlobalMarket(symbol binance.Symbol) types.Market {
|
||||
market := types.Market{
|
||||
Exchange: types.ExchangeBinance,
|
||||
|
|
|
@ -571,6 +571,11 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since
|
|||
return nil, err
|
||||
}
|
||||
|
||||
status, err := toGlobalWithdrawStatus(d.Status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
withdraws = append(withdraws, types.Withdraw{
|
||||
Exchange: types.ExchangeBinance,
|
||||
ApplyTime: types.Time(applyTime),
|
||||
|
@ -581,7 +586,8 @@ func (e *Exchange) QueryWithdrawHistory(ctx context.Context, asset string, since
|
|||
TransactionFee: d.TransactionFee,
|
||||
WithdrawOrderID: d.WithdrawOrderID,
|
||||
Network: d.Network,
|
||||
Status: d.Status.String(),
|
||||
Status: status,
|
||||
OriginalStatus: fmt.Sprintf("%s (%d)", d.Status.String(), int(d.Status)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -340,3 +340,27 @@ func convertWebSocketOrderUpdate(u max.OrderUpdate) (*types.Order, error) {
|
|||
UpdateTime: types.Time(time.Unix(0, u.UpdateTime*int64(time.Millisecond))),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertWithdrawStatus(state max.WithdrawState) types.WithdrawStatus {
|
||||
switch state {
|
||||
|
||||
case max.WithdrawStateSent, max.WithdrawStateSubmitting, max.WithdrawStatePending, "accepted", "approved":
|
||||
return types.WithdrawStatusSent
|
||||
|
||||
case max.WithdrawStateProcessing, "delisted_processing", "kgi_manually_processing", "kgi_manually_confirmed", "sygna_verifying":
|
||||
return types.WithdrawStatusProcessing
|
||||
|
||||
case max.WithdrawStateFailed, "kgi_possible_failed", "rejected", "suspect", "retryable":
|
||||
return types.WithdrawStatusFailed
|
||||
|
||||
case max.WithdrawStateCanceled:
|
||||
return types.WithdrawStatusCancelled
|
||||
|
||||
case "confirmed":
|
||||
// make it compatible with binance
|
||||
return types.WithdrawStatusCompleted
|
||||
|
||||
default:
|
||||
return types.WithdrawStatus(state)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -867,23 +867,7 @@ func (e *Exchange) QueryWithdrawHistory(
|
|||
}
|
||||
|
||||
// we can convert this later
|
||||
status := d.State
|
||||
switch d.State {
|
||||
|
||||
case "confirmed":
|
||||
status = "completed" // make it compatible with binance
|
||||
|
||||
case "submitting", "submitted", "accepted",
|
||||
"rejected", "suspect", "approved", "delisted_processing",
|
||||
"processing", "retryable", "sent", "canceled",
|
||||
"failed", "pending",
|
||||
"kgi_manually_processing", "kgi_manually_confirmed", "kgi_possible_failed",
|
||||
"sygna_verifying":
|
||||
|
||||
default:
|
||||
status = d.State
|
||||
|
||||
}
|
||||
status := convertWithdrawStatus(d.State)
|
||||
|
||||
txIDs[d.TxID] = struct{}{}
|
||||
withdraw := types.Withdraw{
|
||||
|
@ -891,14 +875,16 @@ func (e *Exchange) QueryWithdrawHistory(
|
|||
ApplyTime: types.Time(d.CreatedAt),
|
||||
Asset: toGlobalCurrency(d.Currency),
|
||||
Amount: d.Amount,
|
||||
Address: "",
|
||||
Address: d.Address,
|
||||
AddressTag: "",
|
||||
TransactionID: d.TxID,
|
||||
TransactionFee: d.Fee,
|
||||
TransactionFeeCurrency: d.FeeCurrency,
|
||||
Network: d.NetworkProtocol,
|
||||
// WithdrawOrderID: d.WithdrawOrderID,
|
||||
// Network: d.Network,
|
||||
Status: status,
|
||||
OriginalStatus: string(d.State),
|
||||
}
|
||||
allWithdraws = append(allWithdraws, withdraw)
|
||||
}
|
||||
|
|
|
@ -155,7 +155,23 @@ type WithdrawState string
|
|||
|
||||
const (
|
||||
WithdrawStateSubmitting WithdrawState = "submitting"
|
||||
WithdrawStateSubmitted WithdrawState = "submitted"
|
||||
WithdrawStateConfirmed WithdrawState = "confirmed"
|
||||
WithdrawStatePending WithdrawState = "pending"
|
||||
WithdrawStateProcessing WithdrawState = "processing"
|
||||
WithdrawStateCanceled WithdrawState = "canceled"
|
||||
WithdrawStateFailed WithdrawState = "failed"
|
||||
WithdrawStateSent WithdrawState = "sent"
|
||||
WithdrawStateRejected WithdrawState = "rejected"
|
||||
)
|
||||
|
||||
type WithdrawStatus string
|
||||
|
||||
const (
|
||||
WithdrawStatusPending WithdrawStatus = "pending"
|
||||
WithdrawStatusCancelled WithdrawStatus = "cancelled"
|
||||
WithdrawStatusFailed WithdrawStatus = "failed"
|
||||
WithdrawStatusOK WithdrawStatus = "ok"
|
||||
)
|
||||
|
||||
type Withdraw struct {
|
||||
|
@ -167,14 +183,19 @@ type Withdraw struct {
|
|||
FeeCurrency string `json:"fee_currency"`
|
||||
TxID string `json:"txid"`
|
||||
|
||||
NetworkProtocol string `json:"network_protocol"`
|
||||
Address string `json:"to_address"`
|
||||
|
||||
// State can be "submitting", "submitted",
|
||||
// "rejected", "accepted", "suspect", "approved", "delisted_processing",
|
||||
// "processing", "retryable", "sent", "canceled",
|
||||
// "failed", "pending", "confirmed",
|
||||
// "kgi_manually_processing", "kgi_manually_confirmed", "kgi_possible_failed",
|
||||
// "sygna_verifying"
|
||||
State string `json:"state"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
State WithdrawState `json:"state"`
|
||||
|
||||
Status WithdrawStatus `json:"status,omitempty"`
|
||||
|
||||
CreatedAt types.MillisecondTimestamp `json:"created_at"`
|
||||
UpdatedAt types.MillisecondTimestamp `json:"updated_at"`
|
||||
Notes string `json:"notes"`
|
||||
|
|
|
@ -114,6 +114,34 @@ func (s *Strategy) aggregateBalances(
|
|||
return totalBalances, sessionBalances
|
||||
}
|
||||
|
||||
func (s *Strategy) detectActiveTransfers(ctx context.Context, sessions map[string]*bbgo.ExchangeSession) (bool, error) {
|
||||
var err2 error
|
||||
until := time.Now()
|
||||
since := until.Add(-time.Hour * 24)
|
||||
for _, session := range sessions {
|
||||
transferService, ok := session.Exchange.(types.ExchangeTransferHistoryService)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
withdraws, err := transferService.QueryWithdrawHistory(ctx, "", since, until)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to query withdraw history")
|
||||
err2 = err
|
||||
continue
|
||||
}
|
||||
|
||||
for _, withdraw := range withdraws {
|
||||
switch withdraw.Status {
|
||||
case types.WithdrawStatusProcessing, types.WithdrawStatusSent, types.WithdrawStatusAwaitingApproval:
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, err2
|
||||
}
|
||||
|
||||
func (s *Strategy) selectSessionForCurrency(
|
||||
ctx context.Context, sessions map[string]*bbgo.ExchangeSession, currency string, changeQuantity fixedpoint.Value,
|
||||
) (*bbgo.ExchangeSession, *types.SubmitOrder) {
|
||||
|
@ -391,6 +419,14 @@ func (s *Strategy) align(ctx context.Context, sessions map[string]*bbgo.Exchange
|
|||
}
|
||||
}
|
||||
|
||||
foundActiveTransfer, err := s.detectActiveTransfers(ctx, sessions)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("unable to check active transfers")
|
||||
} else if foundActiveTransfer {
|
||||
log.Warnf("found active transfer, skip balance align check")
|
||||
return
|
||||
}
|
||||
|
||||
totalBalances, sessionBalances := s.aggregateBalances(ctx, sessions)
|
||||
_ = sessionBalances
|
||||
|
||||
|
|
|
@ -149,6 +149,11 @@ type CustomIntervalProvider interface {
|
|||
IsSupportedInterval(interval Interval) bool
|
||||
}
|
||||
|
||||
type ExchangeTransferHistoryService interface {
|
||||
QueryDepositHistory(ctx context.Context, asset string, since, until time.Time) (allDeposits []Deposit, err error)
|
||||
QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []Withdraw, err error)
|
||||
}
|
||||
|
||||
type ExchangeTransferService interface {
|
||||
QueryDepositHistory(ctx context.Context, asset string, since, until time.Time) (allDeposits []Deposit, err error)
|
||||
QueryWithdrawHistory(ctx context.Context, asset string, since, until time.Time) (allWithdraws []Withdraw, err error)
|
||||
|
|
|
@ -7,6 +7,19 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
)
|
||||
|
||||
type WithdrawStatus string
|
||||
|
||||
const (
|
||||
WithdrawStatusSent WithdrawStatus = "sent"
|
||||
WithdrawStatusCancelled WithdrawStatus = "cancelled"
|
||||
WithdrawStatusAwaitingApproval WithdrawStatus = "awaiting_approval"
|
||||
WithdrawStatusRejected WithdrawStatus = "rejected"
|
||||
WithdrawStatusProcessing WithdrawStatus = "processing"
|
||||
WithdrawStatusFailed WithdrawStatus = "failed"
|
||||
WithdrawStatusCompleted WithdrawStatus = "completed"
|
||||
WithdrawStatusUnknown WithdrawStatus = "unknown"
|
||||
)
|
||||
|
||||
type Withdraw struct {
|
||||
GID int64 `json:"gid" db:"gid"`
|
||||
Exchange ExchangeName `json:"exchange" db:"exchange"`
|
||||
|
@ -14,7 +27,8 @@ type Withdraw struct {
|
|||
Amount fixedpoint.Value `json:"amount" db:"amount"`
|
||||
Address string `json:"address" db:"address"`
|
||||
AddressTag string `json:"addressTag"`
|
||||
Status string `json:"status"`
|
||||
Status WithdrawStatus `json:"status"`
|
||||
OriginalStatus string `json:"originalStatus"`
|
||||
|
||||
TransactionID string `json:"transactionID" db:"txn_id"`
|
||||
TransactionFee fixedpoint.Value `json:"transactionFee" db:"txn_fee"`
|
||||
|
|
Loading…
Reference in New Issue
Block a user