decred.org/dcrdex@v1.0.3/client/mm/mm_test.go (about) 1 package mm 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "math/rand" 8 "reflect" 9 "sync" 10 "testing" 11 "time" 12 13 "decred.org/dcrdex/client/asset" 14 "decred.org/dcrdex/client/core" 15 "decred.org/dcrdex/client/db" 16 "decred.org/dcrdex/client/mm/libxc" 17 "decred.org/dcrdex/client/orderbook" 18 "decred.org/dcrdex/dex" 19 "decred.org/dcrdex/dex/order" 20 21 _ "decred.org/dcrdex/client/asset/btc" // register btc asset 22 _ "decred.org/dcrdex/client/asset/dcr" // register dcr asset 23 _ "decred.org/dcrdex/client/asset/eth" // register eth asset 24 _ "decred.org/dcrdex/client/asset/polygon" // register polygon asset 25 ) 26 27 func init() { 28 rand.Seed(time.Now().UnixNano()) 29 } 30 31 type tBookFeed struct { 32 c chan *core.BookUpdate 33 } 34 35 func (t *tBookFeed) Next() <-chan *core.BookUpdate { return t.c } 36 func (t *tBookFeed) Close() {} 37 func (t *tBookFeed) Candles(dur string) error { return nil } 38 39 var _ core.BookFeed = (*tBookFeed)(nil) 40 41 type tCoin struct { 42 coinID []byte 43 value uint64 44 } 45 46 var _ asset.Coin = (*tCoin)(nil) 47 48 func (c *tCoin) ID() dex.Bytes { 49 return c.coinID 50 } 51 func (c *tCoin) String() string { 52 return hex.EncodeToString(c.coinID) 53 } 54 func (c *tCoin) Value() uint64 { 55 return c.value 56 } 57 func (c *tCoin) TxID() string { 58 return hex.EncodeToString(c.coinID) 59 } 60 61 type sendArgs struct { 62 assetID uint32 63 value uint64 64 address string 65 subtract bool 66 } 67 68 type tCore struct { 69 assetBalances map[uint32]*core.WalletBalance 70 assetBalanceErr error 71 market *core.Market 72 singleLotSellFees *OrderFees 73 singleLotBuyFees *OrderFees 74 singleLotFeesErr error 75 multiTradeResult []*core.MultiTradeResult 76 noteFeed chan core.Notification 77 isAccountLocker map[uint32]bool 78 isWithdrawer map[uint32]bool 79 isDynamicSwapper map[uint32]bool 80 cancelsPlaced []order.OrderID 81 multiTradesPlaced []*core.MultiTradeForm 82 maxFundingFees uint64 83 book *orderbook.OrderBook 84 bookFeed *tBookFeed 85 sends []*sendArgs 86 sendCoin *tCoin 87 newDepositAddress string 88 orders map[order.OrderID]*core.Order 89 walletTxsMtx sync.Mutex 90 walletTxs map[string]*asset.WalletTransaction 91 fiatRates map[uint32]float64 92 userParcels uint32 93 parcelLimit uint32 94 exchange *core.Exchange 95 walletStates map[uint32]*core.WalletState 96 } 97 98 func newTCore() *tCore { 99 return &tCore{ 100 assetBalances: make(map[uint32]*core.WalletBalance), 101 noteFeed: make(chan core.Notification), 102 isAccountLocker: make(map[uint32]bool), 103 isWithdrawer: make(map[uint32]bool), 104 isDynamicSwapper: make(map[uint32]bool), 105 cancelsPlaced: make([]order.OrderID, 0), 106 bookFeed: &tBookFeed{ 107 c: make(chan *core.BookUpdate, 1), 108 }, 109 walletTxs: make(map[string]*asset.WalletTransaction), 110 book: &orderbook.OrderBook{}, 111 walletStates: make(map[uint32]*core.WalletState), 112 } 113 } 114 115 var _ clientCore = (*tCore)(nil) 116 117 func (c *tCore) NotificationFeed() *core.NoteFeed { 118 return &core.NoteFeed{C: c.noteFeed} 119 } 120 func (c *tCore) ExchangeMarket(host string, base, quote uint32) (*core.Market, error) { 121 return c.market, nil 122 } 123 124 func (t *tCore) SyncBook(host string, base, quote uint32) (*orderbook.OrderBook, core.BookFeed, error) { 125 return t.book, t.bookFeed, nil 126 } 127 func (*tCore) SupportedAssets() map[uint32]*core.SupportedAsset { 128 return nil 129 } 130 func (c *tCore) SingleLotFees(form *core.SingleLotFeesForm) (uint64, uint64, uint64, error) { 131 if c.singleLotFeesErr != nil { 132 return 0, 0, 0, c.singleLotFeesErr 133 } 134 if c.singleLotSellFees == nil && c.singleLotBuyFees == nil { 135 return 0, 0, 0, fmt.Errorf("no fees set") 136 } 137 138 if form.Sell { 139 return c.singleLotSellFees.Max.Swap, c.singleLotSellFees.Max.Redeem, c.singleLotSellFees.Max.Refund, nil 140 } 141 return c.singleLotBuyFees.Max.Swap, c.singleLotBuyFees.Max.Redeem, c.singleLotBuyFees.Max.Refund, nil 142 } 143 func (c *tCore) Cancel(oidB dex.Bytes) error { 144 var oid order.OrderID 145 copy(oid[:], oidB) 146 c.cancelsPlaced = append(c.cancelsPlaced, oid) 147 return nil 148 } 149 func (c *tCore) AssetBalance(assetID uint32) (*core.WalletBalance, error) { 150 return c.assetBalances[assetID], c.assetBalanceErr 151 } 152 func (c *tCore) MultiTrade(pw []byte, forms *core.MultiTradeForm) []*core.MultiTradeResult { 153 c.multiTradesPlaced = append(c.multiTradesPlaced, forms) 154 return c.multiTradeResult 155 } 156 func (c *tCore) WalletTraits(assetID uint32) (asset.WalletTrait, error) { 157 isAccountLocker := c.isAccountLocker[assetID] 158 isWithdrawer := c.isWithdrawer[assetID] 159 isDynamicSwapper := c.isDynamicSwapper[assetID] 160 161 var traits asset.WalletTrait 162 if isAccountLocker { 163 traits |= asset.WalletTraitAccountLocker 164 } 165 if isWithdrawer { 166 traits |= asset.WalletTraitWithdrawer 167 } 168 if isDynamicSwapper { 169 traits |= asset.WalletTraitDynamicSwapper 170 } 171 172 return traits, nil 173 } 174 func (c *tCore) MaxFundingFees(fromAsset uint32, host string, numTrades uint32, options map[string]string) (uint64, error) { 175 return c.maxFundingFees, nil 176 } 177 func (c *tCore) Login(pw []byte) error { 178 return nil 179 } 180 func (c *tCore) OpenWallet(assetID uint32, pw []byte) error { 181 return nil 182 } 183 func (c *tCore) WalletTransaction(assetID uint32, txID string) (*asset.WalletTransaction, error) { 184 c.walletTxsMtx.Lock() 185 defer c.walletTxsMtx.Unlock() 186 return c.walletTxs[txID], nil 187 } 188 189 func (c *tCore) Network() dex.Network { 190 return dex.Simnet 191 } 192 193 func (c *tCore) FiatConversionRates() map[uint32]float64 { 194 return c.fiatRates 195 } 196 func (c *tCore) Broadcast(core.Notification) {} 197 func (c *tCore) TradingLimits(host string) (userParcels, parcelLimit uint32, err error) { 198 return c.userParcels, c.parcelLimit, nil 199 } 200 201 func (c *tCore) Send(pw []byte, assetID uint32, value uint64, address string, subtract bool) (asset.Coin, error) { 202 c.sends = append(c.sends, &sendArgs{ 203 assetID: assetID, 204 value: value, 205 address: address, 206 subtract: subtract, 207 }) 208 return c.sendCoin, nil 209 } 210 func (c *tCore) NewDepositAddress(assetID uint32) (string, error) { 211 return c.newDepositAddress, nil 212 } 213 func (c *tCore) Order(id dex.Bytes) (*core.Order, error) { 214 var oid order.OrderID 215 copy(oid[:], id) 216 if o, found := c.orders[oid]; found { 217 return o, nil 218 } 219 return nil, fmt.Errorf("order %s not found", id) 220 } 221 222 func (c *tCore) Exchange(host string) (*core.Exchange, error) { 223 return c.exchange, nil 224 } 225 226 func (c *tCore) WalletState(assetID uint32) *core.WalletState { 227 return c.walletStates[assetID] 228 } 229 230 func (c *tCore) setWalletsAndExchange(m *core.Market) { 231 c.walletStates[m.BaseID] = &core.WalletState{ 232 PeerCount: 1, 233 Synced: true, 234 } 235 c.walletStates[m.QuoteID] = &core.WalletState{ 236 PeerCount: 1, 237 Synced: true, 238 } 239 c.exchange = &core.Exchange{ 240 Auth: core.ExchangeAuth{ 241 EffectiveTier: 2, 242 }, 243 } 244 } 245 246 func (c *tCore) setAssetBalances(balances map[uint32]uint64) { 247 c.assetBalances = make(map[uint32]*core.WalletBalance) 248 for assetID, bal := range balances { 249 c.assetBalances[assetID] = &core.WalletBalance{ 250 Balance: &db.Balance{ 251 Balance: asset.Balance{ 252 Available: bal, 253 }, 254 }, 255 } 256 } 257 } 258 259 type dexOrder struct { 260 rate uint64 261 qty uint64 262 sell bool 263 } 264 265 type tBotCoreAdaptor struct { 266 clientCore 267 tCore *tCore 268 269 balances map[uint32]*BotBalance 270 groupedBuys map[uint64][]*core.Order 271 groupedSells map[uint64][]*core.Order 272 orderUpdates chan *core.Order 273 buyFees *OrderFees 274 sellFees *OrderFees 275 fiatExchangeRate uint64 276 buyFeesInBase uint64 277 sellFeesInBase uint64 278 buyFeesInQuote uint64 279 sellFeesInQuote uint64 280 maxBuyQty uint64 281 maxSellQty uint64 282 lastTradePlaced *dexOrder 283 tradeResult *core.Order 284 } 285 286 func (c *tBotCoreAdaptor) DEXBalance(assetID uint32) (*BotBalance, error) { 287 if c.tCore.assetBalanceErr != nil { 288 return nil, c.tCore.assetBalanceErr 289 } 290 return c.balances[assetID], nil 291 } 292 293 func (c *tBotCoreAdaptor) GroupedBookedOrders() (buys, sells map[uint64][]*core.Order) { 294 return c.groupedBuys, c.groupedSells 295 } 296 297 func (c *tBotCoreAdaptor) CancelAllOrders() bool { return false } 298 299 func (c *tBotCoreAdaptor) ExchangeRateFromFiatSources() uint64 { 300 return c.fiatExchangeRate 301 } 302 303 func (c *tBotCoreAdaptor) OrderFees() (buyFees, sellFees *OrderFees, err error) { 304 return c.buyFees, c.sellFees, nil 305 } 306 307 func (c *tBotCoreAdaptor) SubscribeOrderUpdates() (updates <-chan *core.Order) { 308 return c.orderUpdates 309 } 310 311 func (c *tBotCoreAdaptor) OrderFeesInUnits(sell, base bool, rate uint64) (uint64, error) { 312 if sell && base { 313 return c.sellFeesInBase, nil 314 } 315 if sell && !base { 316 return c.sellFeesInQuote, nil 317 } 318 if !sell && base { 319 return c.buyFeesInBase, nil 320 } 321 return c.buyFeesInQuote, nil 322 } 323 324 func (c *tBotCoreAdaptor) SufficientBalanceForDEXTrade(rate, qty uint64, sell bool) (bool, error) { 325 if sell { 326 return qty <= c.maxSellQty, nil 327 } 328 return qty <= c.maxBuyQty, nil 329 } 330 331 func (c *tBotCoreAdaptor) DEXTrade(rate, qty uint64, sell bool) (*core.Order, error) { 332 c.lastTradePlaced = &dexOrder{ 333 rate: rate, 334 qty: qty, 335 sell: sell, 336 } 337 return c.tradeResult, nil 338 } 339 340 func (u *tBotCoreAdaptor) registerFeeGap(s *FeeGapStats) {} 341 342 func (u *tBotCoreAdaptor) checkBotHealth() bool { 343 return true 344 } 345 346 func newTBotCoreAdaptor(c *tCore) *tBotCoreAdaptor { 347 return &tBotCoreAdaptor{ 348 clientCore: c, 349 tCore: c, 350 orderUpdates: make(chan *core.Order), 351 } 352 } 353 354 var _ botCoreAdaptor = (*tBotCoreAdaptor)(nil) 355 356 type tOrderBook struct { 357 midGap uint64 358 midGapErr error 359 360 bidsVWAP map[uint64]vwapResult 361 asksVWAP map[uint64]vwapResult 362 vwapErr error 363 } 364 365 var _ dexOrderBook = (*tOrderBook)(nil) 366 367 func (t *tOrderBook) VWAP(numLots, _ uint64, sell bool) (avg, extrema uint64, filled bool, err error) { 368 if t.vwapErr != nil { 369 return 0, 0, false, t.vwapErr 370 } 371 372 if sell { 373 res, found := t.asksVWAP[numLots] 374 if !found { 375 return 0, 0, false, nil 376 } 377 return res.avg, res.extrema, true, nil 378 } 379 380 res, found := t.bidsVWAP[numLots] 381 if !found { 382 return 0, 0, false, nil 383 } 384 return res.avg, res.extrema, true, nil 385 } 386 387 func (o *tOrderBook) MidGap() (uint64, error) { 388 if o.midGapErr != nil { 389 return 0, o.midGapErr 390 } 391 return o.midGap, nil 392 } 393 394 type tOracle struct { 395 marketPrice float64 396 } 397 398 func (o *tOracle) getMarketPrice(base, quote uint32) float64 { 399 return o.marketPrice 400 } 401 402 type vwapResult struct { 403 avg uint64 404 extrema uint64 405 } 406 407 type withdrawArgs struct { 408 address string 409 amt uint64 410 assetID uint32 411 txID string 412 } 413 414 type tCEX struct { 415 bidsVWAP map[uint64]vwapResult 416 asksVWAP map[uint64]vwapResult 417 vwapErr error 418 balances map[uint32]*libxc.ExchangeBalance 419 balanceErr error 420 tradeID string 421 tradeErr error 422 lastTrade *libxc.Trade 423 cancelledTrades []string 424 cancelTradeErr error 425 tradeUpdates chan *libxc.Trade 426 tradeUpdatesID int 427 depositAddress string 428 withdrawals []*withdrawArgs 429 confirmWithdrawalMtx sync.Mutex 430 confirmWithdrawal *withdrawArgs 431 withdrawalID string 432 confirmDepositMtx sync.Mutex 433 confirmedDeposit *uint64 434 tradeStatus *libxc.Trade 435 } 436 437 func newTCEX() *tCEX { 438 return &tCEX{ 439 bidsVWAP: make(map[uint64]vwapResult), 440 asksVWAP: make(map[uint64]vwapResult), 441 balances: make(map[uint32]*libxc.ExchangeBalance), 442 cancelledTrades: make([]string, 0), 443 tradeUpdates: make(chan *libxc.Trade), 444 } 445 } 446 447 var _ libxc.CEX = (*tCEX)(nil) 448 449 func (c *tCEX) Connect(ctx context.Context) (*sync.WaitGroup, error) { 450 return &sync.WaitGroup{}, nil 451 } 452 func (c *tCEX) Balances(ctx context.Context) (map[uint32]*libxc.ExchangeBalance, error) { 453 return nil, nil 454 } 455 func (c *tCEX) MatchedMarkets(ctx context.Context) ([]*libxc.MarketMatch, error) { 456 return nil, nil 457 } 458 func (c *tCEX) Markets(ctx context.Context) (map[string]*libxc.Market, error) { 459 return nil, nil 460 } 461 func (c *tCEX) Balance(assetID uint32) (*libxc.ExchangeBalance, error) { 462 return c.balances[assetID], c.balanceErr 463 } 464 func (c *tCEX) Trade(ctx context.Context, baseID, quoteID uint32, sell bool, rate, qty uint64, updaterID int) (*libxc.Trade, error) { 465 if c.tradeErr != nil { 466 return nil, c.tradeErr 467 } 468 c.lastTrade = &libxc.Trade{ 469 ID: c.tradeID, 470 BaseID: baseID, 471 QuoteID: quoteID, 472 Rate: rate, 473 Sell: sell, 474 Qty: qty, 475 } 476 return c.lastTrade, nil 477 } 478 func (c *tCEX) CancelTrade(ctx context.Context, seID, quoteID uint32, tradeID string) error { 479 if c.cancelTradeErr != nil { 480 return c.cancelTradeErr 481 } 482 c.cancelledTrades = append(c.cancelledTrades, tradeID) 483 return nil 484 } 485 func (c *tCEX) SubscribeMarket(ctx context.Context, baseID, quoteID uint32) error { 486 return nil 487 } 488 func (c *tCEX) UnsubscribeMarket(baseID, quoteID uint32) error { 489 return nil 490 } 491 func (c *tCEX) VWAP(baseID, quoteID uint32, sell bool, qty uint64) (vwap, extrema uint64, filled bool, err error) { 492 if c.vwapErr != nil { 493 return 0, 0, false, c.vwapErr 494 } 495 496 if sell { 497 res, found := c.asksVWAP[qty] 498 if !found { 499 return 0, 0, false, nil 500 } 501 return res.avg, res.extrema, true, nil 502 } 503 504 res, found := c.bidsVWAP[qty] 505 if !found { 506 return 0, 0, false, nil 507 } 508 return res.avg, res.extrema, true, nil 509 } 510 func (c *tCEX) MidGap(baseID, quoteID uint32) uint64 { return 0 } 511 func (c *tCEX) SubscribeTradeUpdates() (<-chan *libxc.Trade, func(), int) { 512 return c.tradeUpdates, func() {}, c.tradeUpdatesID 513 } 514 func (c *tCEX) GetDepositAddress(ctx context.Context, assetID uint32) (string, error) { 515 return c.depositAddress, nil 516 } 517 518 func (c *tCEX) Withdraw(ctx context.Context, assetID uint32, qty uint64, address string) (string, error) { 519 c.withdrawals = append(c.withdrawals, &withdrawArgs{ 520 address: address, 521 amt: qty, 522 assetID: assetID, 523 }) 524 525 return c.withdrawalID, nil 526 } 527 528 func (c *tCEX) ConfirmWithdrawal(ctx context.Context, withdrawalID string, assetID uint32) (uint64, string, error) { 529 c.confirmWithdrawalMtx.Lock() 530 defer c.confirmWithdrawalMtx.Unlock() 531 532 if c.confirmWithdrawal == nil { 533 return 0, "", libxc.ErrWithdrawalPending 534 } 535 return c.confirmWithdrawal.amt, c.confirmWithdrawal.txID, nil 536 } 537 538 func (c *tCEX) ConfirmDeposit(ctx context.Context, deposit *libxc.DepositData) (bool, uint64) { 539 c.confirmDepositMtx.Lock() 540 defer c.confirmDepositMtx.Unlock() 541 542 if c.confirmedDeposit != nil { 543 return true, *c.confirmedDeposit 544 } 545 return false, 0 546 } 547 548 func (c *tCEX) TradeStatus(ctx context.Context, id string, baseID, quoteID uint32) (*libxc.Trade, error) { 549 return c.tradeStatus, nil 550 } 551 552 func (c *tCEX) Book(baseID, quoteID uint32) (buys, sells []*core.MiniOrder, _ error) { 553 return nil, nil, nil 554 } 555 556 type prepareRebalanceResult struct { 557 rebalance int64 558 cexReserves uint64 559 dexReserves uint64 560 } 561 562 type tBotCexAdaptor struct { 563 balances map[uint32]*BotBalance 564 balanceErr error 565 tradeID string 566 tradeErr error 567 lastTrade *libxc.Trade 568 cancelledTrades []string 569 cancelTradeErr error 570 tradeUpdates chan *libxc.Trade 571 maxBuyQty uint64 572 maxSellQty uint64 573 } 574 575 func newTBotCEXAdaptor() *tBotCexAdaptor { 576 return &tBotCexAdaptor{ 577 balances: make(map[uint32]*BotBalance), 578 cancelledTrades: make([]string, 0), 579 tradeUpdates: make(chan *libxc.Trade), 580 } 581 } 582 583 var _ botCexAdaptor = (*tBotCexAdaptor)(nil) 584 585 var tLogger = dex.StdOutLogger("mm_TEST", dex.LevelInfo) 586 587 func (c *tBotCexAdaptor) CEXBalance(assetID uint32) (*BotBalance, error) { 588 return c.balances[assetID], c.balanceErr 589 } 590 func (c *tBotCexAdaptor) CancelTrade(ctx context.Context, baseID, quoteID uint32, tradeID string) error { 591 if c.cancelTradeErr != nil { 592 return c.cancelTradeErr 593 } 594 c.cancelledTrades = append(c.cancelledTrades, tradeID) 595 return nil 596 } 597 func (c *tBotCexAdaptor) SubscribeMarket(ctx context.Context, baseID, quoteID uint32) error { 598 return nil 599 } 600 func (c *tBotCexAdaptor) SubscribeTradeUpdates() (updates <-chan *libxc.Trade) { 601 return c.tradeUpdates 602 } 603 func (c *tBotCexAdaptor) CEXTrade(ctx context.Context, baseID, quoteID uint32, sell bool, rate, qty uint64) (*libxc.Trade, error) { 604 if c.tradeErr != nil { 605 return nil, c.tradeErr 606 } 607 608 c.lastTrade = &libxc.Trade{ 609 ID: c.tradeID, 610 BaseID: baseID, 611 QuoteID: quoteID, 612 Rate: rate, 613 Sell: sell, 614 Qty: qty, 615 } 616 return c.lastTrade, nil 617 } 618 func (c *tBotCexAdaptor) FreeUpFunds(assetID uint32, cex bool, amt uint64, currEpoch uint64) { 619 } 620 621 func (c *tBotCexAdaptor) MidGap(baseID, quoteID uint32) uint64 { return 0 } 622 func (c *tBotCexAdaptor) SufficientBalanceForCEXTrade(baseID, quoteID uint32, sell bool, rate, qty uint64) bool { 623 if sell { 624 return qty <= c.maxSellQty 625 } 626 return qty <= c.maxBuyQty 627 } 628 629 func (c *tBotCexAdaptor) Book() (_, _ []*core.MiniOrder, _ error) { return nil, nil, nil } 630 631 type tExchangeAdaptor struct { 632 dexBalances map[uint32]*BotBalance 633 cexBalances map[uint32]*BotBalance 634 cfg *BotConfig 635 } 636 637 var _ bot = (*tExchangeAdaptor)(nil) 638 639 func (t *tExchangeAdaptor) Connect(ctx context.Context) (*sync.WaitGroup, error) { 640 return &sync.WaitGroup{}, nil 641 } 642 643 func (t *tExchangeAdaptor) refreshAllPendingEvents(context.Context) {} 644 func (t *tExchangeAdaptor) balances() map[uint32]*BotBalances { 645 return nil 646 } 647 func (t *tExchangeAdaptor) DEXBalance(assetID uint32) *BotBalance { 648 if t.dexBalances[assetID] == nil { 649 return &BotBalance{} 650 } 651 return t.dexBalances[assetID] 652 } 653 func (t *tExchangeAdaptor) CEXBalance(assetID uint32) *BotBalance { 654 if t.cexBalances[assetID] == nil { 655 return &BotBalance{} 656 } 657 return t.cexBalances[assetID] 658 } 659 func (t *tExchangeAdaptor) stats() *RunStats { return nil } 660 func (t *tExchangeAdaptor) updateConfig(cfg *BotConfig) error { 661 t.cfg = cfg 662 return nil 663 } 664 func (t *tExchangeAdaptor) updateInventory(diffs *BotInventoryDiffs) {} 665 func (t *tExchangeAdaptor) timeStart() int64 { return 0 } 666 func (t *tExchangeAdaptor) Book() (buys, sells []*core.MiniOrder, _ error) { 667 return nil, nil, nil 668 } 669 func (t *tExchangeAdaptor) sendStatsUpdate() {} 670 func (t *tExchangeAdaptor) withPause(func() error) error { return nil } 671 func (t *tExchangeAdaptor) botCfg() *BotConfig { return t.cfg } 672 func (t *tExchangeAdaptor) latestEpoch() *EpochReport { return &EpochReport{} } 673 func (t *tExchangeAdaptor) latestCEXProblems() *CEXProblems { return nil } 674 675 func TestAvailableBalances(t *testing.T) { 676 ctx, cancel := context.WithCancel(context.Background()) 677 defer cancel() 678 679 tCore := newTCore() 680 681 ethBtc := &MarketWithHost{ 682 Host: "dex.com", 683 BaseID: 60, 684 QuoteID: 0, 685 } 686 687 dcrBtc := &MarketWithHost{ 688 Host: "dex.com", 689 BaseID: 42, 690 QuoteID: 0, 691 } 692 693 dcrUsdc := &MarketWithHost{ 694 Host: "dex.com", 695 BaseID: 42, 696 QuoteID: 60001, 697 } 698 699 btcUsdc := &MarketWithHost{ 700 Host: "dex.com", 701 BaseID: 0, 702 QuoteID: 60001, 703 } 704 705 cfg := &MarketMakingConfig{ 706 BotConfigs: []*BotConfig{ 707 { 708 Host: "dex.com", 709 BaseID: 42, 710 QuoteID: 0, 711 }, 712 { 713 Host: "dex.com", 714 BaseID: 60, 715 QuoteID: 0, 716 CEXName: libxc.Binance, 717 }, 718 { 719 Host: "dex.com", 720 BaseID: 0, 721 QuoteID: 60001, 722 CEXName: libxc.Binance, 723 }, 724 { 725 Host: "dex.com", 726 BaseID: 42, 727 QuoteID: 60001, 728 CEXName: libxc.BinanceUS, 729 }, 730 }, 731 CexConfigs: []*CEXConfig{ 732 { 733 Name: libxc.Binance, 734 }, 735 { 736 Name: libxc.BinanceUS, 737 }, 738 }, 739 } 740 741 binance := newTCEX() 742 binance.balances = map[uint32]*libxc.ExchangeBalance{ 743 60: {Available: 9e5}, 744 0: {Available: 8e5}, 745 60001: {Available: 6e5}, 746 } 747 748 binanceUS := newTCEX() 749 binanceUS.balances = map[uint32]*libxc.ExchangeBalance{ 750 42: {Available: 7e5}, 751 60001: {Available: 6e5}, 752 } 753 754 mm := MarketMaker{ 755 ctx: ctx, 756 log: tLogger, 757 core: tCore, 758 defaultCfg: cfg, 759 runningBots: make(map[MarketWithHost]*runningBot), 760 } 761 762 mm.cexes = map[string]*centralizedExchange{ 763 libxc.Binance: {CEX: binance, CEXConfig: &CEXConfig{Name: libxc.Binance}}, 764 libxc.BinanceUS: {CEX: binanceUS, CEXConfig: &CEXConfig{Name: libxc.BinanceUS}}, 765 } 766 767 tCore.setAssetBalances(map[uint32]uint64{ 768 42: 9e5, 769 60: 8e5, 770 0: 7e5, 771 60001: 6e5, 772 }) 773 774 checkAvailableBalances := func(mkt *MarketWithHost, expDex, expCex map[uint32]uint64) { 775 t.Helper() 776 dexBalances, cexBalances, err := mm.AvailableBalances(mkt, nil) 777 if err != nil { 778 t.Fatalf("unexpected error: %v", err) 779 } 780 if !reflect.DeepEqual(dexBalances, expDex) { 781 t.Fatalf("unexpected dex balances. wanted %v, got %v", expDex, dexBalances) 782 } 783 if !reflect.DeepEqual(cexBalances, expCex) { 784 t.Fatalf("unexpected cex balances. wanted %v, got %v", expCex, cexBalances) 785 } 786 } 787 788 // No running bots 789 checkAvailableBalances(dcrBtc, map[uint32]uint64{42: 9e5, 0: 7e5}, map[uint32]uint64{}) 790 checkAvailableBalances(ethBtc, map[uint32]uint64{60: 8e5, 0: 7e5}, map[uint32]uint64{60: 9e5, 0: 8e5}) 791 checkAvailableBalances(btcUsdc, map[uint32]uint64{0: 7e5, 60: 8e5, 60001: 6e5}, map[uint32]uint64{0: 8e5, 60001: 6e5}) 792 checkAvailableBalances(dcrUsdc, map[uint32]uint64{42: 9e5, 60: 8e5, 60001: 6e5}, map[uint32]uint64{42: 7e5, 60001: 6e5}) 793 794 rb := &runningBot{ 795 bot: &tExchangeAdaptor{ 796 dexBalances: map[uint32]*BotBalance{ 797 60: {Available: 1e5}, 798 0: {Available: 4e5}, 799 60001: {Available: 2e5}, 800 }, 801 cexBalances: map[uint32]*BotBalance{ 802 60001: {Available: 2e5}, 803 0: {Available: 3e5}, 804 }, 805 cfg: cfg.BotConfigs[1], 806 }, 807 } 808 mm.runningBots[*btcUsdc] = rb 809 810 checkAvailableBalances(dcrBtc, map[uint32]uint64{42: 9e5, 0: 3e5}, map[uint32]uint64{}) 811 checkAvailableBalances(ethBtc, map[uint32]uint64{60: 7e5, 0: 3e5}, map[uint32]uint64{60: 9e5, 0: 5e5}) 812 checkAvailableBalances(btcUsdc, map[uint32]uint64{0: 3e5, 60: 7e5, 60001: 4e5}, map[uint32]uint64{0: 5e5, 60001: 4e5}) 813 checkAvailableBalances(dcrUsdc, map[uint32]uint64{42: 9e5, 60: 7e5, 60001: 4e5}, map[uint32]uint64{42: 7e5, 60001: 6e5}) 814 }