add balance check strategy

This commit is contained in:
narumi 2023-10-24 20:01:53 +08:00
parent 4f35f21581
commit ffcb6f7a81
5 changed files with 183 additions and 0 deletions

9
config/balancecheck.yaml Normal file
View File

@ -0,0 +1,9 @@
---
exchangeStrategies:
- on: max
balancecheck:
exceptedBalances:
BTC: 1.0
USDT: 1000
balanceCheckTorlerance: 20%
cronExpression: "@every 1s"

View File

@ -5,6 +5,7 @@ import (
_ "github.com/c9s/bbgo/pkg/strategy/atrpin" _ "github.com/c9s/bbgo/pkg/strategy/atrpin"
_ "github.com/c9s/bbgo/pkg/strategy/audacitymaker" _ "github.com/c9s/bbgo/pkg/strategy/audacitymaker"
_ "github.com/c9s/bbgo/pkg/strategy/autoborrow" _ "github.com/c9s/bbgo/pkg/strategy/autoborrow"
_ "github.com/c9s/bbgo/pkg/strategy/balancecheck"
_ "github.com/c9s/bbgo/pkg/strategy/bollgrid" _ "github.com/c9s/bbgo/pkg/strategy/bollgrid"
_ "github.com/c9s/bbgo/pkg/strategy/bollmaker" _ "github.com/c9s/bbgo/pkg/strategy/bollmaker"
_ "github.com/c9s/bbgo/pkg/strategy/convert" _ "github.com/c9s/bbgo/pkg/strategy/convert"

View File

@ -0,0 +1,49 @@
package riskcontrol
import (
"fmt"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
log "github.com/sirupsen/logrus"
)
type BalanceCheck struct {
ExpectedBalances map[string]fixedpoint.Value `json:"exceptedBalances"`
BalanceCheckTorlerance fixedpoint.Value `json:"balanceCheckTorlerance"`
}
func (c *BalanceCheck) Validate() error {
if len(c.ExpectedBalances) == 0 {
return fmt.Errorf("expectedBalances is empty")
}
if c.BalanceCheckTorlerance.IsZero() {
return fmt.Errorf("balanceCheckTorlerance is zero")
}
for _, v := range c.ExpectedBalances {
if v.IsZero() {
return fmt.Errorf("expected balance is zero")
}
}
return nil
}
func (c *BalanceCheck) Check(balances types.BalanceMap) error {
for currency, exceptedBalance := range c.ExpectedBalances {
b, ok := balances[currency]
if !ok {
return fmt.Errorf("balance of %s not found", currency)
}
// | (actual - expected) / expected | <= torlerance
balanceErr := exceptedBalance.Sub(b.Available).Div(exceptedBalance).Abs()
log.Infof("[BalanceCheck] %s, expected: %s, actual: %s, error: %.2f%%", currency, exceptedBalance, b.Available, balanceErr.Float64()*100)
if balanceErr.Compare(c.BalanceCheckTorlerance) >= 0 {
return fmt.Errorf("balance of %s is not matched, expected: %s, actual: %s, error: %.2f%%", currency, exceptedBalance, b.Available, balanceErr.Float64()*100)
}
}
return nil
}

View File

@ -0,0 +1,55 @@
package riskcontrol
import (
"testing"
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
"github.com/stretchr/testify/assert"
)
func Test_BalanceCheck(t *testing.T) {
cases := []struct {
Currency string
ExpectedBalance fixedpoint.Value
BalanceCheckTorlerance fixedpoint.Value
ActualBalance fixedpoint.Value
wantErr bool
}{
{
Currency: "USDT",
ExpectedBalance: fixedpoint.NewFromFloat(1_000),
BalanceCheckTorlerance: fixedpoint.NewFromFloat(0.05),
ActualBalance: fixedpoint.NewFromFloat(1_049),
wantErr: false,
},
{
Currency: "USDT",
ExpectedBalance: fixedpoint.NewFromFloat(1_000),
BalanceCheckTorlerance: fixedpoint.NewFromFloat(0.05),
ActualBalance: fixedpoint.NewFromFloat(1_050),
wantErr: true,
},
}
for _, tc := range cases {
expectedBalances := map[string]fixedpoint.Value{
tc.Currency: tc.ExpectedBalance,
}
bc := &BalanceCheck{
ExpectedBalances: expectedBalances,
BalanceCheckTorlerance: tc.BalanceCheckTorlerance,
}
balances := types.BalanceMap{
tc.Currency: types.Balance{
Currency: tc.Currency,
Available: tc.ActualBalance,
},
}
if tc.wantErr {
assert.Error(t, bc.Check(balances))
} else {
assert.NoError(t, bc.Check(balances))
}
}
}

View File

@ -0,0 +1,69 @@
package balancecheck
import (
"context"
"github.com/robfig/cron/v3"
"github.com/sirupsen/logrus"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/risk/riskcontrol"
)
const ID = "balancecheck"
var log = logrus.WithField("strategy", ID)
func init() {
bbgo.RegisterStrategy(ID, &Strategy{})
}
type Strategy struct {
Environment *bbgo.Environment
*riskcontrol.BalanceCheck
CronExpression string `json:"cronExpression"`
cron *cron.Cron
}
func (s *Strategy) Defaults() error {
return nil
}
func (s *Strategy) Initialize() error {
s.BalanceCheck = &riskcontrol.BalanceCheck{
ExpectedBalances: s.ExpectedBalances,
BalanceCheckTorlerance: s.BalanceCheckTorlerance,
}
return nil
}
func (s *Strategy) ID() string {
return ID
}
func (s *Strategy) InstanceID() string {
return ID
}
func (s *Strategy) Validate() error {
if err := s.BalanceCheck.Validate(); err != nil {
return err
}
return nil
}
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {}
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
s.cron = cron.New()
s.cron.AddFunc(s.CronExpression, func() {
balances := session.GetAccount().Balances()
if err := s.Check(balances); err != nil {
log.WithError(err).Error("balance check failed")
}
})
s.cron.Start()
return nil
}