decred.org/dcrdex@v1.0.5/client/mm/config.go (about) 1 package mm 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strconv" 7 8 "decred.org/dcrdex/dex/utils" 9 ) 10 11 // MarketMakingConfig is the overall configuration of the market maker. 12 type MarketMakingConfig struct { 13 BotConfigs []*BotConfig `json:"botConfigs"` 14 CexConfigs []*CEXConfig `json:"cexConfigs"` 15 } 16 17 func (cfg *MarketMakingConfig) Copy() *MarketMakingConfig { 18 c := &MarketMakingConfig{ 19 BotConfigs: make([]*BotConfig, len(cfg.BotConfigs)), 20 CexConfigs: make([]*CEXConfig, len(cfg.CexConfigs)), 21 } 22 copy(c.BotConfigs, cfg.BotConfigs) 23 copy(c.CexConfigs, cfg.CexConfigs) 24 return c 25 } 26 27 // CEXConfig is a configuration for connecting to a CEX API. 28 type CEXConfig struct { 29 // Name is the name of the cex. 30 Name string `json:"name"` 31 // APIKey is the API key for the CEX. 32 APIKey string `json:"apiKey"` 33 // APISecret is the API secret for the CEX. 34 APISecret string `json:"apiSecret"` 35 } 36 37 // AutoRebalanceConfig configures deposits and withdrawals by setting minimum 38 // transfer sizes. Minimum transfer sizes should be set to prevent excessive 39 // fees on high-fee blockchains. To calculate a minimum transfer size for an 40 // asset, choose a fee-loss tolerance <= your profit target. If you only wanted to 41 // lose a maximum of 1% to transfers, and the fees associated with a transfer 42 // are 350 Sats, then your minimum transfer size might be set to 43 // 350 * (1 / 0.01) = 35000 Sats. 44 // For low-fee assets, a transfer size of zero would be perfectly fine in most 45 // cases, but using a higher value prevents churn. 46 // For obvious reasons, minimum transfer sizes should never be more than the 47 // total amount allocated for trading. 48 // The way these are configured will probably be changed to better capture the 49 // reasoning above. 50 type AutoRebalanceConfig struct { 51 MinBaseTransfer uint64 `json:"minBaseTransfer"` 52 MinQuoteTransfer uint64 `json:"minQuoteTransfer"` 53 } 54 55 func (a *AutoRebalanceConfig) copy() *AutoRebalanceConfig { 56 return &AutoRebalanceConfig{ 57 MinBaseTransfer: a.MinBaseTransfer, 58 MinQuoteTransfer: a.MinQuoteTransfer, 59 } 60 } 61 62 // BotBalanceAllocation is the initial allocation of funds for a bot. 63 type BotBalanceAllocation struct { 64 DEX map[uint32]uint64 `json:"dex"` 65 CEX map[uint32]uint64 `json:"cex"` 66 } 67 68 func (b *BotBalanceAllocation) copy() *BotBalanceAllocation { 69 return &BotBalanceAllocation{ 70 DEX: utils.CopyMap(b.DEX), 71 CEX: utils.CopyMap(b.CEX), 72 } 73 } 74 75 // BotInventoryDiffs is the amount of funds to add or remove from a bot's 76 // allocation. 77 type BotInventoryDiffs struct { 78 DEX map[uint32]int64 `json:"dex"` 79 CEX map[uint32]int64 `json:"cex"` 80 } 81 82 func (d *BotInventoryDiffs) copy() *BotInventoryDiffs { 83 return &BotInventoryDiffs{ 84 DEX: utils.CopyMap(d.DEX), 85 CEX: utils.CopyMap(d.CEX), 86 } 87 } 88 89 // balanceDiffsToAllocations converts a BotInventoryDiffs to a 90 // BotBalanceAllocation by removing all negative diffs. 91 func balanceDiffsToAllocation(diffs *BotInventoryDiffs) *BotBalanceAllocation { 92 allocations := &BotBalanceAllocation{ 93 DEX: make(map[uint32]uint64, len(diffs.DEX)), 94 CEX: make(map[uint32]uint64, len(diffs.CEX)), 95 } 96 97 for assetID, diff := range diffs.DEX { 98 if diff > 0 { 99 allocations.DEX[assetID] += uint64(diff) 100 } 101 } 102 for assetID, diff := range diffs.CEX { 103 if diff > 0 { 104 allocations.CEX[assetID] += uint64(diff) 105 } 106 } 107 108 return allocations 109 } 110 111 // #### IMPORTANT ### 112 // If non-backwards compatible changes are made to the BotConfig, a new version 113 // should be created and the event log db should be updated to support both 114 // versions. 115 116 type rpcConfig struct { 117 Alloc *BotBalanceAllocation `json:"alloc"` 118 AutoRebalance *AutoRebalanceConfig `json:"autoRebalance"` 119 } 120 121 func (r *rpcConfig) copy() *rpcConfig { 122 return &rpcConfig{ 123 Alloc: r.Alloc.copy(), 124 AutoRebalance: r.AutoRebalance.copy(), 125 } 126 } 127 128 // BotConfig is the configuration for a market making bot. 129 // The balance fields are the initial amounts that will be reserved to use for 130 // this bot. As the bot trades, the amounts reserved for it will be updated. 131 type BotConfig struct { 132 Host string `json:"host"` 133 BaseID uint32 `json:"baseID"` 134 QuoteID uint32 `json:"quoteID"` 135 136 BaseWalletOptions map[string]string `json:"baseWalletOptions"` 137 QuoteWalletOptions map[string]string `json:"quoteWalletOptions"` 138 139 CEXName string `json:"cexName"` 140 141 // UIConfig is settings defined and used by the front end to determine 142 // allocations. 143 UIConfig json.RawMessage `json:"uiConfig,omitempty"` 144 145 // RPCConfig can be used for file-based initial allocations and 146 // auto-rebalance settings. 147 RPCConfig *rpcConfig `json:"rpcConfig"` 148 149 // LotSize is the lot size of the market at the time this configuration 150 // was created. It is used to notify the user if the lot size changes 151 // when they are starting the bot. 152 LotSize uint64 `json:"lotSize"` 153 154 // Only one of the following configs should be set 155 BasicMMConfig *BasicMarketMakingConfig `json:"basicMarketMakingConfig,omitempty"` 156 SimpleArbConfig *SimpleArbConfig `json:"simpleArbConfig,omitempty"` 157 ArbMarketMakerConfig *ArbMarketMakerConfig `json:"arbMarketMakingConfig,omitempty"` 158 } 159 160 func (c *BotConfig) copy() *BotConfig { 161 b := *c 162 163 b.BaseWalletOptions = utils.CopyMap(c.BaseWalletOptions) 164 b.QuoteWalletOptions = utils.CopyMap(c.QuoteWalletOptions) 165 166 if c.UIConfig != nil { 167 b.UIConfig = make(json.RawMessage, len(c.UIConfig)) 168 copy(b.UIConfig, c.UIConfig) 169 } 170 if c.RPCConfig != nil { 171 b.RPCConfig = c.RPCConfig.copy() 172 } 173 if c.BasicMMConfig != nil { 174 b.BasicMMConfig = c.BasicMMConfig.copy() 175 } 176 if c.SimpleArbConfig != nil { 177 b.SimpleArbConfig = c.SimpleArbConfig.copy() 178 } 179 if c.ArbMarketMakerConfig != nil { 180 b.ArbMarketMakerConfig = c.ArbMarketMakerConfig.copy() 181 } 182 183 return &b 184 } 185 186 // updateLotSize modifies the bot's configuration based on an update to the 187 // market's lot size. 188 func (c *BotConfig) updateLotSize(oldLotSize, newLotSize uint64) { 189 if c.BasicMMConfig != nil { 190 c.BasicMMConfig.updateLotSize(oldLotSize, newLotSize) 191 } else if c.ArbMarketMakerConfig != nil { 192 c.ArbMarketMakerConfig.updateLotSize(oldLotSize, newLotSize) 193 } 194 } 195 196 func (c *BotConfig) validate() error { 197 if c.BasicMMConfig != nil { 198 return c.BasicMMConfig.validate() 199 } else if c.SimpleArbConfig != nil { 200 return c.SimpleArbConfig.validate() 201 } else if c.ArbMarketMakerConfig != nil { 202 return c.ArbMarketMakerConfig.validate() 203 } 204 205 return fmt.Errorf("no bot config set") 206 } 207 208 func validateConfigUpdate(old, new *BotConfig) error { 209 if (old.BasicMMConfig == nil) != (new.BasicMMConfig == nil) || 210 (old.SimpleArbConfig == nil) != (new.SimpleArbConfig == nil) || 211 (old.ArbMarketMakerConfig == nil) != (new.ArbMarketMakerConfig == nil) { 212 return fmt.Errorf("cannot change bot type") 213 } 214 215 return new.validate() 216 } 217 218 func (c *BotConfig) requiresPriceOracle() bool { 219 return c.BasicMMConfig != nil 220 } 221 222 func (c *BotConfig) requiresCEX() bool { 223 return c.SimpleArbConfig != nil || c.ArbMarketMakerConfig != nil 224 } 225 226 // multiSplitBuffer returns the additional buffer to add to the order size 227 // when doing a multi-split. This only applies to the quote asset. 228 func (c *BotConfig) multiSplitBuffer() float64 { 229 if c.QuoteWalletOptions == nil { 230 return 0 231 } 232 multiSplitBuffer, ok := c.QuoteWalletOptions["multisplitbuffer"] 233 if !ok { 234 return 0 235 } 236 multiSplitBufferFloat, err := strconv.ParseFloat(multiSplitBuffer, 64) 237 if err != nil { 238 return 0 239 } 240 return multiSplitBufferFloat 241 } 242 243 // maxPlacements returns the max amount of placements this bot will place on 244 // either side of the market in an epoch. 245 func (c *BotConfig) maxPlacements() (buy, sell uint32) { 246 switch { 247 case c.SimpleArbConfig != nil: 248 return 1, 1 249 case c.ArbMarketMakerConfig != nil: 250 return uint32(len(c.ArbMarketMakerConfig.BuyPlacements)), uint32(len(c.ArbMarketMakerConfig.SellPlacements)) 251 case c.BasicMMConfig != nil: 252 return uint32(len(c.BasicMMConfig.BuyPlacements)), uint32(len(c.BasicMMConfig.SellPlacements)) 253 default: 254 return 1, 1 255 } 256 } 257 258 func dexMarketID(host string, base, quote uint32) string { 259 return fmt.Sprintf("%s-%d-%d", host, base, quote) 260 }