Skip to content

Commit

Permalink
Joe review fixes/Merge Arb
Browse files Browse the repository at this point in the history
  • Loading branch information
martonp committed Sep 21, 2023
1 parent c93b9c4 commit d6cd364
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 176 deletions.
20 changes: 13 additions & 7 deletions client/mm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ const (
Amount
)

// MarketMakingConfig is the overall configuration of the market maker.
type MarketMakingConfig struct {
BotConfigs []*BotConfig `json:"botConfigs"`
CexConfigs []*CEXConfig `json:"cexConfigs"`
}

// CEXConfig is a configuration for connecting to a CEX API.
type CEXConfig struct {
// CEXName is the name of the cex.
CEXName string `json:"cexName"`
// Name is the name of the cex.
Name string `json:"name"`
// APIKey is the API key for the CEX.
APIKey string `json:"apiKey"`
// APISecret is the API secret for the CEX.
Expand All @@ -43,16 +49,16 @@ type BotConfig struct {
QuoteBalance uint64 `json:"quoteBalance"`

// Only one of the following configs should be set
MMCfg *MarketMakingConfig `json:"marketMakingConfig,omitempty"`
MMWithCEXCfg *MarketMakingWithCEXConfig `json:"marketMakingWithCEXConfig,omitempty"`
ArbCfg *SimpleArbConfig `json:"arbConfig,omitempty"`
BasicMMConfig *BasicMarketMakingConfig `json:"basicMarketMakingConfig,omitempty"`
SimpleArbConfig *SimpleArbConfig `json:"simpleArbConfig,omitempty"`
MMWithCEXConfig *MarketMakingWithCEXConfig `json:"marketMakingWithCEXConfig,omitempty"`

Disabled bool `json:"disabled"`
}

func (c *BotConfig) requiresPriceOracle() bool {
if c.MMCfg != nil {
return c.MMCfg.OracleWeighting != nil && *c.MMCfg.OracleWeighting > 0
if c.BasicMMConfig != nil {
return c.BasicMMConfig.OracleWeighting != nil && *c.BasicMMConfig.OracleWeighting > 0
}
return false
}
Expand Down
77 changes: 46 additions & 31 deletions client/mm/mm.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,18 @@ type MarketMaker struct {

// NewMarketMaker creates a new MarketMaker.
func NewMarketMaker(c clientCore, cfgPath string, log dex.Logger) (*MarketMaker, error) {
if _, err := os.Stat(cfgPath); err != nil {
cfg := new(MarketMakingConfig)
cfgB, err := json.Marshal(cfg)
if err != nil {
return nil, fmt.Errorf("failed to marshal empty config file: %w", err)
}
err = os.WriteFile(cfgPath, cfgB, 0644)
if err != nil {
return nil, fmt.Errorf("failed to write empty config file: %w", err)
}
}

return &MarketMaker{
core: c,
log: log,
Expand Down Expand Up @@ -837,7 +849,7 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s
if alternateConfigPath != nil {
path = *alternateConfigPath
}
botCfgs, err := getMarketMakingConfig(path)
cfg, err := getMarketMakingConfig(path)
if err != nil {
return fmt.Errorf("error getting market making config: %v", err)
}
Expand All @@ -851,16 +863,16 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s

m.ctx, m.die = context.WithCancel(ctx)

enabledCfgs, err := validateAndFilterEnabledConfigs(botCfgs)
enabledBots, err := validateAndFilterEnabledConfigs(cfg.BotConfigs)
if err != nil {
return err
}

if err := m.loginAndUnlockWallets(pw, enabledCfgs); err != nil {
if err := m.loginAndUnlockWallets(pw, enabledBots); err != nil {
return err
}

oracle, err := priceOracleFromConfigs(m.ctx, enabledCfgs, m.log.SubLogger("PriceOracle"))
oracle, err := priceOracleFromConfigs(m.ctx, enabledBots, m.log.SubLogger("PriceOracle"))
if err != nil {
return err
}
Expand All @@ -874,7 +886,7 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s
m.syncedOracleMtx.Unlock()
}()

if err := m.setupBalances(enabledCfgs); err != nil {
if err := m.setupBalances(enabledBots); err != nil {
return err
}

Expand Down Expand Up @@ -905,10 +917,10 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s
}()

var cexCfgMap map[string]*CEXConfig
if len(cexCfgs) > 0 {
cexCfgMap = make(map[string]*CEXConfig, len(cexCfgs))
for _, cexCfg := range cexCfgs {
cexCfgMap[cexCfg.CEXName] = cexCfg
if len(cfg.CexConfigs) > 0 {
cexCfgMap = make(map[string]*CEXConfig, len(cfg.CexConfigs))
for _, cexCfg := range cfg.CexConfigs {
cexCfgMap[cexCfg.Name] = cexCfg
}
}

Expand Down Expand Up @@ -939,9 +951,9 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s
return cex, nil
}

for _, cfg := range enabledCfgs {
for _, cfg := range enabledBots {
switch {
case cfg.MMCfg != nil:
case cfg.BasicMMConfig != nil:
wg.Add(1)
go func(cfg *BotConfig) {
defer wg.Done()
Expand All @@ -964,12 +976,12 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s
}
RunBasicMarketMaker(m.ctx, cfg, m.wrappedCoreForBot(mktID), oracle, baseFiatRate, quoteFiatRate, logger)
}(cfg)
case cfg.ArbCfg != nil:
case cfg.SimpleArbConfig != nil:
wg.Add(1)
go func(cfg *BotConfig) {
defer wg.Done()
logger := m.log.SubLogger(fmt.Sprintf("Arbitrage-%s-%d-%d", cfg.Host, cfg.BaseAsset, cfg.QuoteAsset))
cex, err := getConnectedCEX(cfg.ArbCfg.CEXName)
cex, err := getConnectedCEX(cfg.SimpleArbConfig.CEXName)
if err != nil {
logger.Errorf("failed to connect to CEX: %v", err)
return
Expand All @@ -995,44 +1007,47 @@ func (m *MarketMaker) Run(ctx context.Context, pw []byte, alternateConfigPath *s
return nil
}

func getMarketMakingConfig(path string) ([]*BotConfig, error) {
cfg := []*BotConfig{}
func getMarketMakingConfig(path string) (*MarketMakingConfig, error) {
if path == "" {
return cfg, nil
return nil, fmt.Errorf("no config file provided")
}

data, err := os.ReadFile(path)
if err == nil {
return cfg, json.Unmarshal(data, &cfg)
if err != nil {
return nil, err
}
if os.IsNotExist(err) {
return cfg, nil

cfg := &MarketMakingConfig{}
err = json.Unmarshal(data, cfg)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("error reading file: %w", err)

return cfg, nil
}

// GetMarketMakingConfig returns the market making config.
func (m *MarketMaker) GetMarketMakingConfig() ([]*BotConfig, error) {
func (m *MarketMaker) GetMarketMakingConfig() (*MarketMakingConfig, error) {
return getMarketMakingConfig(m.cfgPath)
}

// UpdateMarketMakingConfig updates the market making config for one of the
// bots.
func (m *MarketMaker) UpdateMarketMakingConfig(updatedCfg *BotConfig) ([]*BotConfig, error) {
// UpdateMarketMakingConfig updates the configuration for one of the bots.
func (m *MarketMaker) UpdateBotConfig(updatedCfg *BotConfig) (*MarketMakingConfig, error) {
cfg, err := m.GetMarketMakingConfig()
if err != nil {
return nil, fmt.Errorf("error getting market making config: %v", err)
}

var updated bool
for i, c := range cfg {
for i, c := range cfg.BotConfigs {
if c.Host == updatedCfg.Host && c.QuoteAsset == updatedCfg.QuoteAsset && c.BaseAsset == updatedCfg.BaseAsset {
cfg[i] = updatedCfg
cfg.BotConfigs[i] = updatedCfg
updated = true
break
}
}
if !updated {
cfg = append(cfg, updatedCfg)
cfg.BotConfigs = append(cfg.BotConfigs, updatedCfg)
}

data, err := json.MarshalIndent(cfg, "", " ")
Expand All @@ -1048,16 +1063,16 @@ func (m *MarketMaker) UpdateMarketMakingConfig(updatedCfg *BotConfig) ([]*BotCon
}

// RemoveConfig removes a bot config from the market making config.
func (m *MarketMaker) RemoveConfig(host string, baseID, quoteID uint32) ([]*BotConfig, error) {
func (m *MarketMaker) RemoveBotConfig(host string, baseID, quoteID uint32) (*MarketMakingConfig, error) {
cfg, err := m.GetMarketMakingConfig()
if err != nil {
return nil, fmt.Errorf("error getting market making config: %v", err)
}

var updated bool
for i, c := range cfg {
for i, c := range cfg.BotConfigs {
if c.Host == host && c.QuoteAsset == quoteID && c.BaseAsset == baseID {
cfg = append(cfg[:i], cfg[i+1:]...)
cfg.BotConfigs = append(cfg.BotConfigs[:i], cfg.BotConfigs[i+1:]...)
updated = true
break
}
Expand Down
18 changes: 9 additions & 9 deletions client/mm/mm_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ type OrderPlacement struct {
GapFactor float64 `json:"gapFactor"`
}

// MarketMakingConfig is the configuration for a simple market
// BasicMarketMakingConfig is the configuration for a simple market
// maker that places orders on both sides of the order book.
type MarketMakingConfig struct {
type BasicMarketMakingConfig struct {
// GapStrategy selects an algorithm for calculating the distance from
// the basis price to place orders.
GapStrategy GapStrategy `json:"gapStrategy"`
Expand Down Expand Up @@ -111,7 +111,7 @@ func needBreakEvenHalfSpread(strat GapStrategy) bool {
return strat == GapStrategyAbsolutePlus || strat == GapStrategyPercentPlus || strat == GapStrategyMultiplier
}

func (c *MarketMakingConfig) Validate() error {
func (c *BasicMarketMakingConfig) Validate() error {
if c.OracleBias < -0.05 || c.OracleBias > 0.05 {
return fmt.Errorf("bias %f out of bounds", c.OracleBias)
}
Expand Down Expand Up @@ -205,7 +205,7 @@ type basicMarketMaker struct {
host string
base uint32
quote uint32
cfg *MarketMakingConfig
cfg *BasicMarketMakingConfig
book dexOrderBook
log dex.Logger
core clientCore
Expand Down Expand Up @@ -279,7 +279,7 @@ func (m *basicMarketMaker) groupedOrders() (buys, sells map[int][]*groupedOrder)
// or oracle weighting is 0, the fiat rate is used.
// If there is no fiat rate available, the empty market rate in the
// configuration is used.
func basisPrice(book dexOrderBook, oracle oracle, cfg *MarketMakingConfig, mkt *core.Market, fiatRate uint64, log dex.Logger) uint64 {
func basisPrice(book dexOrderBook, oracle oracle, cfg *BasicMarketMakingConfig, mkt *core.Market, fiatRate uint64, log dex.Logger) uint64 {
midGap, err := book.MidGap()
if err != nil && !errors.Is(err, orderbook.ErrEmptyOrderbook) {
log.Errorf("MidGap error: %v", err)
Expand Down Expand Up @@ -504,7 +504,7 @@ type rateLots struct {
placementIndex int
}

func basicMMRebalance(newEpoch uint64, m rebalancer, c clientCore, cfg *MarketMakingConfig, mkt *core.Market, buyFees,
func basicMMRebalance(newEpoch uint64, m rebalancer, c clientCore, cfg *BasicMarketMakingConfig, mkt *core.Market, buyFees,
sellFees *orderFees, log dex.Logger) (cancels []dex.Bytes, buyOrders, sellOrders []*rateLots) {
basisPrice := m.basisPrice()
if basisPrice == 0 {
Expand Down Expand Up @@ -855,13 +855,13 @@ func (m *basicMarketMaker) run() {

// RunBasicMarketMaker starts a basic market maker bot.
func RunBasicMarketMaker(ctx context.Context, cfg *BotConfig, c clientCore, oracle oracle, baseFiatRate, quoteFiatRate float64, log dex.Logger) {
if cfg.MMCfg == nil {
if cfg.BasicMMConfig == nil {
// implies bug in caller
log.Errorf("No market making config provided. Exiting.")
return
}

err := cfg.MMCfg.Validate()
err := cfg.BasicMMConfig.Validate()
if err != nil {
c.Broadcast(newValidationErrorNote(cfg.Host, cfg.BaseAsset, cfg.QuoteAsset, fmt.Sprintf("invalid market making config: %v", err)))
return
Expand All @@ -877,7 +877,7 @@ func RunBasicMarketMaker(ctx context.Context, cfg *BotConfig, c clientCore, orac
ctx: ctx,
core: c,
log: log,
cfg: cfg.MMCfg,
cfg: cfg.BasicMMConfig,
host: cfg.Host,
base: cfg.BaseAsset,
quote: cfg.QuoteAsset,
Expand Down
Loading

0 comments on commit d6cd364

Please sign in to comment.