Skip to content

Commit

Permalink
Add bot config for normal strategy (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
ashleyvega committed Mar 3, 2021
1 parent 3d0c9b7 commit d760c16
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 12 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 🤖 Liquidity Bot

`liqbot` is a Golang bot app that uses Vega liquidity provision orders.

## Licence

Distributed under the MIT License. See `LICENSE` for more information.
10 changes: 8 additions & 2 deletions bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"code.vegaprotocol.io/go-wallet/wallet"
ppconfig "code.vegaprotocol.io/priceproxy/config"
ppservice "code.vegaprotocol.io/priceproxy/service"
"github.com/pkg/errors"
)

// Bot is the generic bot interface.
Expand All @@ -28,9 +29,14 @@ type PricingEngine interface {
func New(config config.BotConfig, pe PricingEngine, ws wallet.WalletHandler) (b Bot, err error) {
switch config.Strategy {
case "normal":
b = normal.New(config, pe, ws)
b, err = normal.New(config, pe, ws)
default:
err = fmt.Errorf("unrecognised bot strategy: %s", config.Strategy)
err = errors.New("unrecognised bot strategy")
}
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to create new bot with strategy %s", config.Strategy))
return
}

return
}
166 changes: 166 additions & 0 deletions bot/normal/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package normal

import (
"fmt"

"code.vegaprotocol.io/liqbot/config"

"github.com/hashicorp/go-multierror"
"github.com/pkg/errors"
)

const (
keyExpectedMarkPrice = "expectedMarkPrice"
keyAuctionVolume = "auctionVolume"
keyMaxLong = "maxLong"
keyMaxShort = "maxShort"
keyPosManagementFraction = "posManagementFraction"
keyStakeFraction = "stakeFraction"
keyOrdersFraction = "ordersFraction"
keyShorteningShape = "shorteningShape"
keyLongeningShape = "longeningShape"
keyPosManagementSleepMilliseconds = "posManagementSleepMilliseconds"
keyMarketPriceSteeringRatePerSecond = "marketPriceSteeringRatePerSecond"
keyLimitOrderDistributionParams = "limitOrderDistributionParams"
keyTargetLNVol = "targetLNVol"
)

// ShapeConfig is ... TBD. May be replaced with a struct from Vega Core.
type ShapeConfig struct{}

// LODParamsConfig is ... TBD: a little data structure which sets the algo and params for how limits
// orders are generated.
type LODParamsConfig struct{}

// Strategy configures the normal strategy.
type Strategy struct {
// ExpectedMarkPrice (optional) specifies the expected mark price for a market that may not yet
// have a mark price. It is used to calculate margin cost of orders meeting liquidity
// requirement.
ExpectedMarkPrice uint64

// AuctionVolume ...
AuctionVolume uint64

// MaxLong specifies the maximum long position that the bot will tolerate.
MaxLong uint64

// MaxShort specifies the maximum short position that the bot will tolerate.
MaxShort uint64

// PosManagementFraction controls the size of market orders used to manage the bot's position.
PosManagementFraction float64

// StakeFraction (along with OrdersFraction) is used in rule-of-thumb heuristics to decide how
// the bot should deploy collateral.
StakeFraction float64

// OrdersFraction (along with StakeFraction) is used in rule-of-thumb heuristics to decide how
// the bot should deploy collateral.
OrdersFraction float64

// ShorteningShape (which includes both sides of the book) specifies the shape used when the bot
// is trying to shorten its position.
ShorteningShape *ShapeConfig

// LongeningShape (which includes both sides of the book) specifies the shape used when the bot
// is trying to lengthen its position. Note that the initial shape used by the bot is always the
// longening shape, because being long is a little cheaper in position margin than being short.
LongeningShape *ShapeConfig

// PosManagementSleepMilliseconds is the sleep time, in milliseconds, between position management
PosManagementSleepMilliseconds uint64

// MarketPriceSteeringRatePerSecond ...
MarketPriceSteeringRatePerSecond float64

// LimitOrderDistributionParams ...
LimitOrderDistributionParams *LODParamsConfig

// TargetLNVol specifies the target log-normal volatility (e.g. 0.5 for 50%).
TargetLNVol float64
}

func readStrategyConfig(details map[string]string) (s *Strategy, err error) {
s = &Strategy{}
errInvalid := "invalid strategy config for %s"

var errs *multierror.Error

s.ExpectedMarkPrice, err = config.ReadUint64(details, keyExpectedMarkPrice)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyExpectedMarkPrice)))
}

s.AuctionVolume, err = config.ReadUint64(details, keyAuctionVolume)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyAuctionVolume)))
}

s.MaxLong, err = config.ReadUint64(details, keyMaxLong)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyMaxLong)))
}

s.MaxShort, err = config.ReadUint64(details, keyMaxShort)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyMaxShort)))
}

s.PosManagementFraction, err = config.ReadFloat64(details, keyPosManagementFraction)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyPosManagementFraction)))
}

s.StakeFraction, err = config.ReadFloat64(details, keyStakeFraction)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyStakeFraction)))
}

s.OrdersFraction, err = config.ReadFloat64(details, keyOrdersFraction)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyOrdersFraction)))
}

// ShorteningShape TBD

// LongeningShape TBD

s.PosManagementSleepMilliseconds, err = config.ReadUint64(details, keyPosManagementSleepMilliseconds)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyPosManagementSleepMilliseconds)))
}

s.MarketPriceSteeringRatePerSecond, err = config.ReadFloat64(details, keyMarketPriceSteeringRatePerSecond)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyMarketPriceSteeringRatePerSecond)))
}

// LimitOrderDistributionParams TBD

s.TargetLNVol, err = config.ReadFloat64(details, keyTargetLNVol)
if err != nil {
errs = multierror.Append(errs, errors.Wrap(err, fmt.Sprintf(errInvalid, keyTargetLNVol)))
}

err = errs.ErrorOrNil()
return
}

func (s *Strategy) String() string {
return fmt.Sprintf("normal.Strategy{ExpectedMarkPrice=%d, AuctionVolume=%d, MaxLong=%d, MaxShort=%d, PosManagementFraction=%f, StakeFraction=%f, OrdersFraction=%f, ShorteningShape=TBD(*ShapeConfig), LongeningShape=TBD(*ShapeConfig), PosManagementSleepMilliseconds=%d, MarketPriceSteeringRatePerSecond=%f, LimitOrderDistributionParams=TBD(*LODParamsConfig), TargetLNVol=%f}",
s.ExpectedMarkPrice,
s.AuctionVolume,
s.MaxLong,
s.MaxShort,
s.PosManagementFraction,
s.StakeFraction,
s.OrdersFraction,
// s.ShorteningShape,
// s.LongeningShape,
s.PosManagementSleepMilliseconds,
s.MarketPriceSteeringRatePerSecond,
// s.LimitOrderDistributionParams,
s.TargetLNVol,
)
}
30 changes: 20 additions & 10 deletions bot/normal/normal.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ type PricingEngine interface {
GetPrice(pricecfg ppconfig.PriceConfig) (pi ppservice.PriceResponse, err error)
}

// LiqBot represents one liquidity bot.
type LiqBot struct {
// Bot represents one Normal liquidity bot.
type Bot struct {
config config.BotConfig
active bool
log *log.Entry
pricingEngine PricingEngine
settlementAsset string
stop chan bool
strategy *Strategy
market *proto.Market
node Node

Expand All @@ -59,9 +60,9 @@ type LiqBot struct {
mu sync.Mutex
}

// New returns a new instance of LiqBot.
func New(config config.BotConfig, pe PricingEngine, ws wallet.WalletHandler) *LiqBot {
lb := LiqBot{
// New returns a new instance of Bot.
func New(config config.BotConfig, pe PricingEngine, ws wallet.WalletHandler) (b *Bot, err error) {
b = &Bot{
config: config,
log: log.WithFields(log.Fields{
"bot": config.Name,
Expand All @@ -71,11 +72,20 @@ func New(config config.BotConfig, pe PricingEngine, ws wallet.WalletHandler) *Li
walletServer: ws,
}

return &lb
b.strategy, err = readStrategyConfig(config.StrategyDetails)
if err != nil {
err = errors.Wrap(err, "failed to read strategy details")
return
}
b.log.WithFields(log.Fields{
"strategy": b.strategy.String(),
}).Debug("read strategy config")

return
}

// Start starts the liquidity bot goroutine(s).
func (lb *LiqBot) Start() error {
func (lb *Bot) Start() error {
lb.mu.Lock()
err := lb.setupWallet()
if err != nil {
Expand Down Expand Up @@ -120,7 +130,7 @@ func (lb *LiqBot) Start() error {
}

// Stop stops the liquidity bot goroutine(s).
func (lb *LiqBot) Stop() {
func (lb *Bot) Stop() {
lb.mu.Lock()
active := lb.active
lb.mu.Unlock()
Expand All @@ -137,7 +147,7 @@ func (lb *LiqBot) Stop() {
close(lb.stop)
}

func (lb *LiqBot) run() {
func (lb *Bot) run() {
for {
select {
case <-lb.stop:
Expand Down Expand Up @@ -182,7 +192,7 @@ func (lb *LiqBot) run() {
}
}

func (lb *LiqBot) setupWallet() (err error) {
func (lb *Bot) setupWallet() (err error) {
// no need to acquire lb.mu, it's already held.
lb.walletPassphrase = "DCBAabcd1357!#&*" + lb.config.Name

Expand Down
21 changes: 21 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/url"
"strconv"
"time"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -142,3 +143,23 @@ func ConfigureLogging(cfg *ServerConfig) error {
}
return nil
}

// ReadFloat64 extracts a float64 from a strategy config map.
func ReadFloat64(details map[string]string, key string) (v float64, err error) {
value, found := details[key]
if !found {
err = errors.New("missing config")
return
}
return strconv.ParseFloat(value, 64)
}

// ReadUint64 extracts a uint64 from a strategy config map.
func ReadUint64(details map[string]string, key string) (v uint64, err error) {
value, found := details[key]
if !found {
err = errors.New("missing config")
return
}
return strconv.ParseUint(value, 0, 64)
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
code.vegaprotocol.io/go-wallet v0.6.2
code.vegaprotocol.io/priceproxy v0.0.2
github.com/golang/mock v1.5.0
github.com/hashicorp/go-multierror v1.1.0
github.com/jinzhu/configor v1.2.1
github.com/julienschmidt/httprouter v1.3.0
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
Expand Down

0 comments on commit d760c16

Please sign in to comment.