diff --git a/configs/config.go b/configs/config.go index 0de6e09..299dd2b 100644 --- a/configs/config.go +++ b/configs/config.go @@ -21,7 +21,6 @@ type General struct { type MarketMaker struct { StartQty float64 `yaml:"StartQty"` StepQty float64 `yaml:"StepQty"` - EndQty int64 `yaml:"EndQty"` ProfitThreshold int64 `yaml:"ProfitThreshold"` Interval time.Duration `yaml:"Interval"` Slippage float64 `yaml:"Slippage"` diff --git a/configs/default.go b/configs/default.go index 72513a6..91fc5e7 100644 --- a/configs/default.go +++ b/configs/default.go @@ -11,10 +11,9 @@ func DefaultConfig() Config { LogLevel: "info", }, MarketMaker: MarketMaker{ - StartQty: 10.0, - StepQty: 20.0, - EndQty: 400, // max trade DAI in strategy0 and strategy1 - ProfitThreshold: 50000, // 50_000 TMN + StartQty: 1, + StepQty: 1, + ProfitThreshold: 5_000, // 50_000 TMN Interval: time.Minute * 10, Slippage: 0.001, }, @@ -39,7 +38,7 @@ func DefaultConfig() Config { Nobitex: Nobitex{ Url: "https://api.nobitex.ir", Key: "", // Assuming no default value for Key - MinimumOrderToman: 300000, + MinimumOrderToman: 300_000, Timeout: time.Second * 60, // 60s OrderStatusInterval: time.Second * 2, // 2s RetryTimeOut: time.Second * 360, // 360s diff --git a/internal/cmd/run/run.go b/internal/cmd/run/run.go index cc49ccd..a5f8035 100644 --- a/internal/cmd/run/run.go +++ b/internal/cmd/run/run.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" + "github.com/rs/zerolog" "github.com/shopspring/decimal" "github.com/spf13/cobra" @@ -27,44 +28,50 @@ import ( "github.com/zarbanio/market-maker-keeper/internal/nobitex" "github.com/zarbanio/market-maker-keeper/internal/strategy" "github.com/zarbanio/market-maker-keeper/internal/uniswapv3" - "github.com/zarbanio/market-maker-keeper/pkg/logger" "github.com/zarbanio/market-maker-keeper/store" ) +var ( + Logger zerolog.Logger +) + func main(cfg configs.Config) { - postgresStore := store.NewPostgres(cfg.Postgres.Host, cfg.Postgres.Port, cfg.Postgres.User, cfg.Postgres.Password, cfg.Postgres.DB) - err := postgresStore.Migrate(cfg.Postgres.MigrationsPath) + lvl, err := zerolog.ParseLevel(cfg.General.LogLevel) if err != nil { - log.Panic(err) + lvl = zerolog.InfoLevel } - err = logger.InitLogger(context.Background(), postgresStore, cfg.General.LogLevel) + + Logger = zerolog.New(os.Stdout).With().Timestamp().Caller().Logger().Level(lvl) + + postgresStore := store.NewPostgres(cfg.Postgres.Host, cfg.Postgres.Port, cfg.Postgres.User, cfg.Postgres.Password, cfg.Postgres.DB) + err = postgresStore.Migrate(cfg.Postgres.MigrationsPath) if err != nil { log.Panic(err) } blockPtr := blockptr.NewDBBlockPointer(postgresStore, cfg.Indexer.StartBlock) if !blockPtr.Exists() { - logger.Logger.Debug().Msg("block pointer doest not exits. creating a new one") + Logger.Debug().Msg("block pointer doest not exits. creating a new one") err := blockPtr.Create() if err != nil { - logger.Logger.Fatal().Err(err).Msg("error creating block pointer") + Logger.Fatal().Err(err).Msg("error creating block pointer") } - logger.Logger.Debug().Uint64("start block", cfg.Indexer.StartBlock).Msg("new block pointer created.") + Logger.Debug().Uint64("start block", cfg.Indexer.StartBlock).Msg("new block pointer created.") } privateKey := os.Getenv("PRIVATE_KEY") if privateKey == "" { - logger.Logger.Fatal().Msg("PRIVATE_KEY environment variable is not set") + Logger.Fatal().Msg("PRIVATE_KEY environment variable is not set") } executorWallet, err := keystore.New(privateKey) if err != nil { - logger.Logger.Fatal().Err(err).Msg("error while initializing new executor wallet") + Logger.Fatal().Err(err).Msg("error while initializing new executor wallet") } eth, err := ethclient.Dial(cfg.Chain.Url) if err != nil { - logger.Logger.Fatal().Err(err).Msg("error while dialing eth client") + Logger.Fatal().Err(err).Msg("error while dialing eth client") } tokenStore := mstore.NewMemoryTokenStore() @@ -80,13 +87,13 @@ func main(cfg configs.Config) { for _, t := range cfg.Tokens { sym, err := symbol.FromString(t.Symbol) if err != nil { - logger.Logger.Panic().Err(err).Msg("error while converting symbol type") + Logger.Panic().Err(err).Msg("error while converting symbol type") } token := erc20.NewToken(common.HexToAddress(t.Address), sym, int64(t.Decimals)) erc20Client := erc20.New(eth, token) err = tokenStore.AddToken(token) if err != nil { - logger.Logger.Panic().Err(err).Msg("error while adding new token in token store") + Logger.Panic().Err(err).Msg("error while adding new token in token store") } dexTrader.AddERC20Client(erc20Client) } @@ -95,25 +102,25 @@ func main(cfg configs.Config) { dai, err := tokenStore.GetTokenBySymbol(symbol.DAI) if err != nil { - logger.Logger.Panic().Err(err).Msg("error while getting token by symbol") + Logger.Panic().Err(err).Msg("error while getting token by symbol") } zar, err := tokenStore.GetTokenBySymbol(symbol.ZAR) if err != nil { - logger.Logger.Panic().Err(err).Msg("error while getting token by symbol") + Logger.Panic().Err(err).Msg("error while getting token by symbol") } // crate pair in database if not exist botPair := pair.Pair{QuoteAsset: dai.Symbol(), BaseAsset: zar.Symbol()} pairId, err := postgresStore.CreatePairIfNotExist(context.Background(), &botPair) if err != nil { - logger.Logger.Panic().Err(err).Msg("error while creating pair") + Logger.Panic().Err(err).Msg("error while creating pair") } botPair.Id = pairId poolFee := domain.ParseUniswapFee(cfg.Uniswap.PoolFee) _, err = uniswapV3Factory.GetPool(context.Background(), dai.Address(), zar.Address(), poolFee) if err != nil { - logger.Logger.Panic().Err(err).Msg("error while getting pool from uniswapV3") + Logger.Panic().Err(err).Msg("error while getting pool from uniswapV3") } quoter := uniswapv3.NewQuoter(eth, common.HexToAddress(cfg.Contracts.UniswapV3Quoter)) @@ -154,19 +161,53 @@ func main(cfg configs.Config) { strategyConfig := strategy.Config{ StartQty: decimal.NewFromFloat(cfg.MarketMaker.StartQty), StepQty: decimal.NewFromFloat(cfg.MarketMaker.StepQty), - EndQty: decimal.NewFromInt(cfg.MarketMaker.EndQty), ProfitThreshold: decimal.NewFromInt(cfg.MarketMaker.ProfitThreshold), Slippage: decimal.NewFromFloat(cfg.MarketMaker.Slippage), } - buyDaiInUniswapSellTetherInNobitex := strategy.NewBuyDaiUniswapSellTetherNobitex(postgresStore, nobitexExchange, dexTrader, quoter, tokens, strategyConfig) - buyTetherInNobitexSellDaiInUniswap := strategy.NewSellDaiUniswapBuyTetherNobitex(postgresStore, nobitexExchange, dexTrader, quoter, tokens, strategyConfig) + Logger.Info(). + Str("startQty", strategyConfig.StartQty.String()). + Str("stepQty", strategyConfig.StepQty.String()). + Str("profitThreshold", strategyConfig.ProfitThreshold.String()). + Str("slippage", strategyConfig.Slippage.String()). + Msg("market maker started") + + buyDaiInUniswapSellTetherInNobitex := &strategy.BuyDaiUniswapSellTetherNobitex{ + Store: postgresStore, + Nobitex: nobitexExchange, + DexQuoter: quoter, + DexTrader: dexTrader, + Tokens: tokens, + UniswapFee: domain.UniswapFeeFee01, + Marketsdata: make(map[strategy.Market]strategy.MarketData), + Config: strategyConfig, + } + buyTetherInNobitexSellDaiInUniswap := &strategy.SellDaiUniswapBuyTetherNobitex{ + Store: postgresStore, + Nobitex: nobitexExchange, + UniswapQuoter: quoter, + DexTrader: dexTrader, + Tokens: tokens, + UniswapFee: domain.UniswapFeeFee01, + Marketsdata: make(map[strategy.Market]strategy.MarketData), + Config: strategyConfig, + } ctx := context.Background() strategies := []strategy.ArbitrageStrategy{buyTetherInNobitexSellDaiInUniswap, buyDaiInUniswapSellTetherInNobitex} - exec := executor.NewExecutor(postgresStore, pairId, strategies, nobitexExchange, *dexTrader, *indexer, cfg.Nobitex.RetryTimeOut, cfg.Nobitex.RetrySleepDuration) + exec := &executor.Executor{ + Store: postgresStore, + Strategies: strategies, + PairId: pairId, + Nobitex: nobitexExchange, + DexTrader: *dexTrader, + Indxer: *indexer, + Logger: Logger, + NobitexRetryTimeOut: cfg.Nobitex.RetryTimeOut, + NobitexSleepDuration: cfg.Nobitex.RetrySleepDuration, + } ticker := time.NewTicker(cfg.MarketMaker.Interval) done := make(chan bool) @@ -177,23 +218,23 @@ func main(cfg configs.Config) { for { lastCycleId, err := postgresStore.GetLastCycleId(ctx) if err != nil { - logger.Logger.Fatal().Err(err).Msg("error while getting last cycle Id") + Logger.Fatal().Err(err).Msg("error while getting last cycle Id") } cycleId := lastCycleId + 1 err = postgresStore.CreateCycle(ctx, time.Now(), domain.CycleStatusRunning) if err != nil { - logger.Logger.Fatal().Err(err).Msg("error while creating new cycle") + Logger.Fatal().Err(err).Msg("error while creating new cycle") } exec.SetCycleId(cycleId) - logger.Logger.Info().Int64("cycleId", cycleId).Msg("new cycle started") + Logger.Info().Int64("cycleId", cycleId).Msg("new cycle started") exec.RunAll() status := domain.CycleStatusSuccess err = postgresStore.UpdateCycle(ctx, cycleId, time.Now(), status) if err != nil { - logger.Logger.Fatal().Err(err).Msg("error while updating cycle") + Logger.Fatal().Err(err).Msg("error while updating cycle") } - logger.Logger.Info().Int64("cycleId", cycleId).Msg("cycle finished") + Logger.Info().Int64("cycleId", cycleId).Msg("cycle finished") select { case <-done: return diff --git a/internal/executor/exec.go b/internal/executor/exec.go index 41937b4..018d071 100644 --- a/internal/executor/exec.go +++ b/internal/executor/exec.go @@ -7,6 +7,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/rs/zerolog" "github.com/zarbanio/market-maker-keeper/internal/chain" "github.com/zarbanio/market-maker-keeper/internal/dextrader" "github.com/zarbanio/market-maker-keeper/internal/domain" @@ -14,54 +15,30 @@ import ( "github.com/zarbanio/market-maker-keeper/internal/domain/symbol" "github.com/zarbanio/market-maker-keeper/internal/domain/transaction" "github.com/zarbanio/market-maker-keeper/internal/strategy" - "github.com/zarbanio/market-maker-keeper/pkg/logger" "github.com/zarbanio/market-maker-keeper/store" ) type Executor struct { - s store.IStore - strategies []strategy.ArbitrageStrategy - nobitex domain.Exchange - dexTrader dextrader.Wrapper - indxer chain.Indexer - cycleId int64 - pairId int64 - orderId int64 - transactionId int64 - nobitexRetryTimeOut time.Duration - nobitexSleepDuration time.Duration - uniswapFee domain.UniswapFee -} - -func NewExecutor( - s store.IStore, - pairId int64, - strategies []strategy.ArbitrageStrategy, - nobitex domain.Exchange, - dexTrader dextrader.Wrapper, - indxer chain.Indexer, - nobitexRetryTimeOut time.Duration, - nobitexSleepDuration time.Duration, - -) *Executor { - return &Executor{ - s: s, - pairId: pairId, - strategies: strategies, - nobitex: nobitex, - dexTrader: dexTrader, - indxer: indxer, - nobitexRetryTimeOut: nobitexRetryTimeOut, - nobitexSleepDuration: nobitexSleepDuration, - uniswapFee: domain.UniswapFeeFee01, - } + Store store.IStore + Strategies []strategy.ArbitrageStrategy + Nobitex domain.Exchange + DexTrader dextrader.Wrapper + Indxer chain.Indexer + CycleId int64 + PairId int64 + OrderId int64 + TransactionId int64 + NobitexRetryTimeOut time.Duration + NobitexSleepDuration time.Duration + UniswapFee domain.UniswapFee + Logger zerolog.Logger } func (e *Executor) RunAll() { - for _, strategy := range e.strategies { + for _, strategy := range e.Strategies { err := e.Run(strategy) if err != nil { - logger.Logger.Error().Err(err).Str("strategy", strategy.Name()).Msg("failed to run strategy") + e.Logger.Error().Err(err).Str("strategy", strategy.Name()).Msg("failed to run strategy") } } } @@ -71,22 +48,22 @@ func (e *Executor) Run(strategy strategy.ArbitrageStrategy) error { if err != nil { return fmt.Errorf("failed to setup strategy %s. %w", strategy.Name(), err) } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategy.Name()).Object("marketdata", marketdata).Msg("strategy setup completed.") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategy.Name()).Object("marketdata", marketdata).Msg("strategy setup completed.") opportunity, err := strategy.Evaluate(context.Background()) if err != nil { return fmt.Errorf("failed to evaluate strategy %s. %w", strategy.Name(), err) } if opportunity == nil { - logger.Logger.Info(). - Int64("cycleId", e.cycleId). + e.Logger.Info(). + Int64("cycleId", e.CycleId). Str("strategy", strategy.Name()). Msg("no profitable opportunity found.") return nil } - logger.Logger.Info(). - Int64("cycleId", e.cycleId). + e.Logger.Info(). + Int64("cycleId", e.CycleId). Str("strategy", strategy.Name()). Object("bestArbirageOpportunity", opportunity). Msg("a profitable opportunity found.") @@ -101,7 +78,7 @@ func (e *Executor) Run(strategy strategy.ArbitrageStrategy) error { return fmt.Errorf("failed to execute strategy %s. %w", strategy.Name(), err) } - _, err = e.s.CreateNewTrade(context.Background(), e.pairId, e.orderId, e.transactionId) + _, err = e.Store.CreateNewTrade(context.Background(), e.PairId, e.OrderId, e.TransactionId) if err != nil { return err } @@ -111,7 +88,7 @@ func (e *Executor) Run(strategy strategy.ArbitrageStrategy) error { return err } - err = e.s.CreateArbitrageOpporchunity(context.Background(), data) + err = e.Store.CreateArbitrageOpporchunity(context.Background(), data) if err != nil { return err } @@ -132,7 +109,7 @@ func (e *Executor) Execute(strategyName string, orderCandidate strategy.OrderCan } func (e *Executor) SetCycleId(cycleId int64) { - e.cycleId = cycleId + e.CycleId = cycleId } func (e *Executor) ExecuteUniswap(strategyName string, orderCandidate strategy.OrderCandidate) error { @@ -140,24 +117,24 @@ func (e *Executor) ExecuteUniswap(strategyName string, orderCandidate strategy.O txID int64 txHash common.Hash ) - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Object("orderCandidate", orderCandidate).Msg("executing uniswap order candidate") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Object("orderCandidate", orderCandidate).Msg("executing uniswap order candidate") - tx, err := e.dexTrader.Trade(orderCandidate.Source(), orderCandidate.Destination(), e.uniswapFee, orderCandidate.In, orderCandidate.MinOut) + tx, err := e.DexTrader.Trade(orderCandidate.Source(), orderCandidate.Destination(), e.UniswapFee, orderCandidate.In, orderCandidate.MinOut) if err != nil { return err } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Str("transaction", tx.Hash().String()).Msg("transaction sent to uniswap.") - txID, err = e.s.CreateTransaction(context.Background(), tx, e.dexTrader.GetExecutorAddress()) + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Str("transaction", tx.Hash().String()).Msg("transaction sent to uniswap.") + txID, err = e.Store.CreateTransaction(context.Background(), tx, e.DexTrader.GetExecutorAddress()) if err != nil { return err } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Int64("transaction_id", txID).Msg("transaction created in database.") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Int64("transaction_id", txID).Msg("transaction created in database.") txHash = tx.Hash() - e.transactionId = txID + e.TransactionId = txID - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Str("transaction", tx.Hash().String()).Msg("waiting for transaction receipt.") - rec, header, err := e.indxer.WaitForReceipt(context.Background(), txHash) + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Str("transaction", tx.Hash().String()).Msg("waiting for transaction receipt.") + rec, header, err := e.Indxer.WaitForReceipt(context.Background(), txHash) if err != nil { return err } @@ -167,11 +144,11 @@ func (e *Executor) ExecuteUniswap(strategyName string, orderCandidate strategy.O Timestamp: time.Unix(int64(header.Time), 0), Status: transaction.CastFromReceiptStatus(rec.Status), } - err = e.s.UpdateTransaction(context.Background(), txID, txUpdate) + err = e.Store.UpdateTransaction(context.Background(), txID, txUpdate) if err != nil { return err } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Str("transaction", tx.Hash().String()).Msg("transaction receipt received.") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Str("transaction", tx.Hash().String()).Msg("transaction receipt received.") return nil } @@ -191,9 +168,9 @@ func (e *Executor) ExecuteNobitex(strategyName string, orderCandidate strategy.O Status: order.Draft, } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Object("order", o).Msg("executing nobitex order candidate") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Object("order", o).Msg("executing nobitex order candidate") - id, t, err := e.nobitex.PlaceOrder(o) + id, t, err := e.Nobitex.PlaceOrder(o) if err != nil { return fmt.Errorf("failed to place order: %w", err) } @@ -201,39 +178,39 @@ func (e *Executor) ExecuteNobitex(strategyName string, orderCandidate strategy.O return fmt.Errorf("invalid order id: %d", id) } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Int64("nobitexOrderId", id).Msg("order placed in nobitex.") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Int64("nobitexOrderId", id).Msg("order placed in nobitex.") nobitexOrderId = id o.CreatedAt = t o.OrderId = nobitexOrderId o.Status = order.Open - orderID, err := e.s.CreateNewOrder(context.Background(), o) + orderID, err := e.Store.CreateNewOrder(context.Background(), o) orderId = orderID if err != nil { return fmt.Errorf("failed to create order: %w", err) } - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Int64("orderId", orderId).Msg("order created in database.") + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Int64("orderId", orderId).Msg("order created in database.") - e.orderId = orderID + e.OrderId = orderID - logger.Logger.Info().Int64("cycleId", e.cycleId).Str("strategy", strategyName).Int64("nobitexOrderId", nobitexOrderId).Msg("waiting for nobitex order status.") - ctx, cancel := context.WithTimeout(context.Background(), e.nobitexRetryTimeOut) + e.Logger.Info().Int64("cycleId", e.CycleId).Str("strategy", strategyName).Int64("nobitexOrderId", nobitexOrderId).Msg("waiting for nobitex order status.") + ctx, cancel := context.WithTimeout(context.Background(), e.NobitexRetryTimeOut) defer cancel() for { select { case <-ctx.Done(): - logger.Logger.Error().Int64("nobitexOrderId", nobitexOrderId).Msg("nobitex order status timeout") + e.Logger.Error().Int64("nobitexOrderId", nobitexOrderId).Msg("nobitex order status timeout") return ctx.Err() default: - orderUpdate, err := e.nobitex.OrderStatus(context.Background(), nobitexOrderId) + orderUpdate, err := e.Nobitex.OrderStatus(context.Background(), nobitexOrderId) if err != nil { return err } if orderUpdate.Status != order.Filled { continue } - logger.Logger.Info().Int64("nobitexOrderId", nobitexOrderId).Str("status", orderUpdate.Status.String()).Msg("nobitex order status received.") + e.Logger.Info().Int64("nobitexOrderId", nobitexOrderId).Str("status", orderUpdate.Status.String()).Msg("nobitex order status received.") update := order.UpdatedFields{ Status: &orderUpdate.Status, Price: &orderUpdate.Price, @@ -244,7 +221,7 @@ func (e *Executor) ExecuteNobitex(strategyName string, orderCandidate strategy.O UnmatchedAmount: &orderUpdate.UnmatchedAmount, CreatedAt: &orderUpdate.CreatedAt, } - err = e.s.UpdateOrder(context.Background(), orderId, update) + err = e.Store.UpdateOrder(context.Background(), orderId, update) if err != nil { return err } diff --git a/internal/strategy/buy_uniswap_sell_nobitex.go b/internal/strategy/buy_uniswap_sell_nobitex.go index 43563d0..d877500 100644 --- a/internal/strategy/buy_uniswap_sell_nobitex.go +++ b/internal/strategy/buy_uniswap_sell_nobitex.go @@ -33,39 +33,15 @@ func (e ErrorInsufficentBalance) Error() string { } type BuyDaiUniswapSellTetherNobitex struct { - s store.IStore - nobitex domain.Exchange - dexQuoter *uniswapv3.Quoter - dexTrader *dextrader.Wrapper - tokens map[symbol.Symbol]domain.Token - uniswapFee domain.UniswapFee - - marketsdata map[Market]MarketData - config Config -} - -func NewBuyDaiUniswapSellTetherNobitex( - s store.IStore, - exchange domain.Exchange, - dexTrader *dextrader.Wrapper, - dexQuoter *uniswapv3.Quoter, - tokens map[symbol.Symbol]domain.Token, - config Config, -) ArbitrageStrategy { - marketsdata := make(map[Market]MarketData) - marketsdata[UniswapV3] = NewMarketData() - marketsdata[Nobitex] = NewMarketData() - - return &BuyDaiUniswapSellTetherNobitex{ - s: s, - uniswapFee: domain.UniswapFeeFee01, - nobitex: exchange, - dexQuoter: dexQuoter, - dexTrader: dexTrader, - config: config, - tokens: tokens, - marketsdata: marketsdata, - } + Store store.IStore + Nobitex domain.Exchange + DexQuoter *uniswapv3.Quoter + DexTrader *dextrader.Wrapper + Tokens map[symbol.Symbol]domain.Token + UniswapFee domain.UniswapFee + + Marketsdata map[Market]MarketData + Config Config } func (s *BuyDaiUniswapSellTetherNobitex) Name() string { @@ -73,6 +49,11 @@ func (s *BuyDaiUniswapSellTetherNobitex) Name() string { } func (s *BuyDaiUniswapSellTetherNobitex) Setup() (MarketsData, error) { + if s.Marketsdata == nil { + marketsdata := make(map[Market]MarketData) + marketsdata[Nobitex] = NewMarketData() + marketsdata[UniswapV3] = NewMarketData() + } _, err := s.getNobitexBalances() if err != nil { return nil, err @@ -85,53 +66,55 @@ func (s *BuyDaiUniswapSellTetherNobitex) Setup() (MarketsData, error) { if err != nil { return nil, err } - return s.marketsdata, nil + return s.Marketsdata, nil } func (s *BuyDaiUniswapSellTetherNobitex) getNobitexBalances() (map[symbol.Symbol]decimal.Decimal, error) { - balances, err := s.nobitex.Balances() + balances, err := s.Nobitex.Balances() if err != nil { return nil, fmt.Errorf("failed to get nobitex balances. %w", err) } for _, balance := range balances { - s.marketsdata[Nobitex].Balances[balance.Symbol] = balance.Balance + s.Marketsdata[Nobitex].Balances[balance.Symbol] = balance.Balance } - return s.marketsdata[Nobitex].Balances, nil + return s.Marketsdata[Nobitex].Balances, nil } func (s *BuyDaiUniswapSellTetherNobitex) getDexTraderBalances() (map[symbol.Symbol]decimal.Decimal, error) { - balances, err := s.dexTrader.GetTokenBalances(context.Background()) + balances, err := s.DexTrader.GetTokenBalances(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get dex trader balances. %w", err) } for _, balance := range balances { - s.marketsdata[UniswapV3].Balances[balance.Symbol] = balance.Balance + if balance.Balance.IsZero() { + continue + } + s.Marketsdata[UniswapV3].Balances[balance.Symbol] = balance.Balance } - return s.marketsdata[UniswapV3].Balances, nil + return s.Marketsdata[UniswapV3].Balances, nil } func (s *BuyDaiUniswapSellTetherNobitex) getNobitexPrices() (map[symbol.Symbol]decimal.Decimal, error) { - ethPrice, err := s.nobitex.ExchangeRate(symbol.ETH, symbol.IRT) + ethPrice, err := s.Nobitex.ExchangeRate(symbol.ETH, symbol.IRT) if err != nil { return nil, fmt.Errorf("failed to get eth price. %w", err) } - tetherPrice, err := s.nobitex.ExchangeRate(symbol.USDT, symbol.IRT) + tetherPrice, err := s.Nobitex.ExchangeRate(symbol.USDT, symbol.IRT) if err != nil { return nil, fmt.Errorf("failed to get tether price. %w", err) } - s.marketsdata[Nobitex].Prices[symbol.ETH] = ethPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman - s.marketsdata[Nobitex].Prices[symbol.USDT] = tetherPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman - return s.marketsdata[Nobitex].Prices, nil + s.Marketsdata[Nobitex].Prices[symbol.ETH] = ethPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman + s.Marketsdata[Nobitex].Prices[symbol.USDT] = tetherPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman + return s.Marketsdata[Nobitex].Prices, nil } func (s *BuyDaiUniswapSellTetherNobitex) Evaluate(ctx context.Context) (*ArbitrageOpportunity, error) { - if s.marketsdata == nil { - return nil, fmt.Errorf("markets data is nil") - } - - startQty := s.config.StartQty // in dai - endQty := decimal.Min(s.config.EndQty, s.marketsdata[Nobitex].Balances[symbol.USDT]) // in dai, we can't buy more than what we have in tether in nobitex - stepQty := s.config.StepQty // in dai + startQty := s.Config.StartQty // in dai + endQty := decimal.Min( + s.Marketsdata[Nobitex].Balances[symbol.USDT], + s.Marketsdata[UniswapV3].Balances[symbol.DAI], + ) + stepQty := s.Config.StepQty // in dai var bestArbirageOpportunity *ArbitrageOpportunity @@ -139,7 +122,6 @@ func (s *BuyDaiUniswapSellTetherNobitex) Evaluate(ctx context.Context) (*Arbitra uniswapV3OrderCandidate, err := s.findUniswapOrderCandidate(ctx, qty) if err != nil { if errors.Is(err, ErrorInsufficentBalance{}) { - logger.Logger.Warn().Err(err).Msg("not enough balance to find the best arbitrage opportunity.") break } return nil, err @@ -148,7 +130,6 @@ func (s *BuyDaiUniswapSellTetherNobitex) Evaluate(ctx context.Context) (*Arbitra nobitexOrderCandidate, err := s.findNobitexOrderCandidate(ctx, qty) if err != nil { if errors.Is(err, ErrorInsufficentBalance{}) { - logger.Logger.Warn().Err(err).Msg("not enough balance to find the best arbitrage opportunity.") break } if errors.Is(err, ErrorInvalidAmount) { @@ -184,18 +165,18 @@ func (s *BuyDaiUniswapSellTetherNobitex) findUniswapOrderCandidate(ctx context.C // amountIn is in zar // amountOut is in dai // we are buying dai with zar - tokenIn := s.tokens[symbol.ZAR] - tokenOut := s.tokens[symbol.DAI] - tetherPrice := s.marketsdata[Nobitex].Prices[symbol.USDT] - etherPrice := s.marketsdata[Nobitex].Prices[symbol.ETH] - zarBalance := s.marketsdata[UniswapV3].Balances[symbol.ZAR] + tokenIn := s.Tokens[symbol.ZAR] + tokenOut := s.Tokens[symbol.DAI] + tetherPrice := s.Marketsdata[Nobitex].Prices[symbol.USDT] + etherPrice := s.Marketsdata[Nobitex].Prices[symbol.ETH] + zarBalance := s.Marketsdata[UniswapV3].Balances[symbol.ZAR] - in, err := s.dexQuoter.GetSwapInputWithExactOutput(ctx, tokenIn, tokenOut, s.uniswapFee, qty) + in, err := s.DexQuoter.GetSwapInputWithExactOutput(ctx, tokenIn, tokenOut, s.UniswapFee, qty) if err != nil { return nil, fmt.Errorf("failed to get swap output with exact output: %w", err) } - dexTraderGasFee, err := s.dexTrader.EstimateDexTradeGasFee(tokenIn, tokenOut, s.uniswapFee.BigInt(), in, qty) + dexTraderGasFee, err := s.DexTrader.EstimateDexTradeGasFee(tokenIn, tokenOut, s.UniswapFee.BigInt(), in, qty) if err != nil { // If the dexTrader's assets are less than the specified quantity, // the EstimateDexTradeGasFee function will return an "execution reverted: STF" error. @@ -242,12 +223,12 @@ func (s *BuyDaiUniswapSellTetherNobitex) findNobitexOrderCandidate(ctx context.C // amountIn is in tether // amountOut is in toman // we are selling tether for toman - takerFee := s.nobitex.Fees(trade.Taker) - minOrder := s.nobitex.MinimumOrderToman() - tetherPrice := s.marketsdata[Nobitex].Prices[symbol.USDT] - tetherBalance := s.marketsdata[Nobitex].Balances[symbol.USDT] + takerFee := s.Nobitex.Fees(trade.Taker) + minOrder := s.Nobitex.MinimumOrderToman() + tetherPrice := s.Marketsdata[Nobitex].Prices[symbol.USDT] + tetherBalance := s.Marketsdata[Nobitex].Balances[symbol.USDT] - orderBook, err := s.nobitex.OrderBook(symbol.USDT, symbol.IRT) + orderBook, err := s.Nobitex.OrderBook(symbol.USDT, symbol.IRT) if err != nil { return nil, fmt.Errorf("failed to get nobitex order book: %w", err) } @@ -293,5 +274,5 @@ func (s *BuyDaiUniswapSellTetherNobitex) Teardown() { marketsdata := make(map[Market]MarketData) marketsdata[UniswapV3] = NewMarketData() marketsdata[Nobitex] = NewMarketData() - s.marketsdata = marketsdata + s.Marketsdata = marketsdata } diff --git a/internal/strategy/sell_uniswap_buy_nobitex.go b/internal/strategy/sell_uniswap_buy_nobitex.go index 130dbb7..14f9b68 100644 --- a/internal/strategy/sell_uniswap_buy_nobitex.go +++ b/internal/strategy/sell_uniswap_buy_nobitex.go @@ -17,39 +17,15 @@ import ( ) type SellDaiUniswapBuyTetherNobitex struct { - s store.IStore - nobitex domain.Exchange - uniswapQuoter *uniswapv3.Quoter - dexTrader *dextrader.Wrapper - tokens map[symbol.Symbol]domain.Token - uniswapFee domain.UniswapFee - - marketsdata MarketsData - config Config -} - -func NewSellDaiUniswapBuyTetherNobitex( - s store.IStore, - exchange domain.Exchange, - dexTrader *dextrader.Wrapper, - uniswapQuoter *uniswapv3.Quoter, - tokens map[symbol.Symbol]domain.Token, - config Config, -) ArbitrageStrategy { - marketsdata := make(map[Market]MarketData) - marketsdata[UniswapV3] = NewMarketData() - marketsdata[Nobitex] = NewMarketData() - - return &SellDaiUniswapBuyTetherNobitex{ - s: s, - nobitex: exchange, - uniswapQuoter: uniswapQuoter, - dexTrader: dexTrader, - config: config, - tokens: tokens, - uniswapFee: domain.UniswapFeeFee01, - marketsdata: marketsdata, - } + Store store.IStore + Nobitex domain.Exchange + UniswapQuoter *uniswapv3.Quoter + DexTrader *dextrader.Wrapper + Tokens map[symbol.Symbol]domain.Token + UniswapFee domain.UniswapFee + + Marketsdata MarketsData + Config Config } func (s *SellDaiUniswapBuyTetherNobitex) Name() string { @@ -57,6 +33,11 @@ func (s *SellDaiUniswapBuyTetherNobitex) Name() string { } func (s *SellDaiUniswapBuyTetherNobitex) Setup() (MarketsData, error) { + if s.Marketsdata == nil { + marketsdata := make(map[Market]MarketData) + marketsdata[Nobitex] = NewMarketData() + marketsdata[UniswapV3] = NewMarketData() + } _, err := s.getNobitexBalances() if err != nil { return nil, err @@ -69,53 +50,59 @@ func (s *SellDaiUniswapBuyTetherNobitex) Setup() (MarketsData, error) { if err != nil { return nil, err } - return s.marketsdata, nil + return s.Marketsdata, nil } func (s *SellDaiUniswapBuyTetherNobitex) getNobitexBalances() (map[symbol.Symbol]decimal.Decimal, error) { - balances, err := s.nobitex.Balances() + balances, err := s.Nobitex.Balances() if err != nil { return nil, fmt.Errorf("failed to get nobitex balances. %w", err) } for _, balance := range balances { - s.marketsdata[Nobitex].Balances[balance.Symbol] = balance.Balance + s.Marketsdata[Nobitex].Balances[balance.Symbol] = balance.Balance } - return s.marketsdata[Nobitex].Balances, nil + return s.Marketsdata[Nobitex].Balances, nil } func (s *SellDaiUniswapBuyTetherNobitex) getDexTraderBalances() (map[symbol.Symbol]decimal.Decimal, error) { - balances, err := s.dexTrader.GetTokenBalances(context.Background()) + balances, err := s.DexTrader.GetTokenBalances(context.Background()) if err != nil { return nil, fmt.Errorf("failed to get dex trader balances. %w", err) } for _, balance := range balances { - s.marketsdata[UniswapV3].Balances[balance.Symbol] = balance.Balance + if balance.Balance.IsZero() { + continue + } + s.Marketsdata[UniswapV3].Balances[balance.Symbol] = balance.Balance } - return s.marketsdata[UniswapV3].Balances, nil + return s.Marketsdata[UniswapV3].Balances, nil } func (s *SellDaiUniswapBuyTetherNobitex) getNobitexPrices() (map[symbol.Symbol]decimal.Decimal, error) { - ethPrice, err := s.nobitex.ExchangeRate(symbol.ETH, symbol.IRT) + ethPrice, err := s.Nobitex.ExchangeRate(symbol.ETH, symbol.IRT) if err != nil { return nil, fmt.Errorf("failed to get eth price. %w", err) } - tetherPrice, err := s.nobitex.ExchangeRate(symbol.USDT, symbol.IRT) + tetherPrice, err := s.Nobitex.ExchangeRate(symbol.USDT, symbol.IRT) if err != nil { return nil, fmt.Errorf("failed to get tether price. %w", err) } - s.marketsdata[Nobitex].Prices[symbol.ETH] = ethPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman - s.marketsdata[Nobitex].Prices[symbol.USDT] = tetherPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman - return s.marketsdata[Nobitex].Prices, nil + s.Marketsdata[Nobitex].Prices[symbol.ETH] = ethPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman + s.Marketsdata[Nobitex].Prices[symbol.USDT] = tetherPrice.Div(decimal.NewFromInt(10)) // convert from rial to toman + return s.Marketsdata[Nobitex].Prices, nil } func (s *SellDaiUniswapBuyTetherNobitex) Evaluate(ctx context.Context) (*ArbitrageOpportunity, error) { - if s.marketsdata == nil { + if s.Marketsdata == nil { return nil, fmt.Errorf("markets data is nil") } - startQty := s.config.StartQty - endQty := decimal.Min(s.config.EndQty) - stepQty := s.config.StepQty + startQty := s.Config.StartQty + endQty := decimal.Min( + s.Marketsdata[Nobitex].Balances[symbol.USDT], + s.Marketsdata[UniswapV3].Balances[symbol.DAI], + ) + stepQty := s.Config.StepQty var bestArbirageOpportunity *ArbitrageOpportunity @@ -129,7 +116,7 @@ func (s *SellDaiUniswapBuyTetherNobitex) Evaluate(ctx context.Context) (*Arbitra return nil, fmt.Errorf("failed to find uniswap order candidate. %w", err) } - nobitexOrderCandidate, err := s.findNobitexOrderCandidate(ctx, qty) + nobitexOrderCandidate, err := s.findNobitexOrderCandidate(qty) if err != nil { if errors.Is(err, ErrorInsufficentBalance{}) { logger.Logger.Warn().Err(err).Msg("not enough balance to find the best arbitrage opportunity.") @@ -169,13 +156,13 @@ func (s *SellDaiUniswapBuyTetherNobitex) findUniswapOrderCandidate(ctx context.C // amountIn is in dai // amountOut is in zar // we are selling dai - tokenIn := s.tokens[symbol.DAI] - tokenOut := s.tokens[symbol.ZAR] - etherPrice := s.marketsdata[Nobitex].Prices[symbol.ETH] - usdtPrice := s.marketsdata[Nobitex].Prices[symbol.USDT] - daiBalance := s.marketsdata[UniswapV3].Balances[symbol.DAI] + tokenIn := s.Tokens[symbol.DAI] + tokenOut := s.Tokens[symbol.ZAR] + etherPrice := s.Marketsdata[Nobitex].Prices[symbol.ETH] + usdtPrice := s.Marketsdata[Nobitex].Prices[symbol.USDT] + daiBalance := s.Marketsdata[UniswapV3].Balances[symbol.DAI] - out, err := s.uniswapQuoter.GetSwapOutputWithExactInput(ctx, tokenIn, tokenOut, s.uniswapFee, qty) + out, err := s.UniswapQuoter.GetSwapOutputWithExactInput(ctx, tokenIn, tokenOut, s.UniswapFee, qty) if err != nil { return nil, fmt.Errorf("failed to get swap output with exact input: %w", err) } @@ -184,9 +171,9 @@ func (s *SellDaiUniswapBuyTetherNobitex) findUniswapOrderCandidate(ctx context.C return nil, errors.New("not valid quantity for this liquidity") } - minOut := out.Mul(decimal.NewFromInt(1).Sub(s.config.Slippage)).Round(int32(tokenOut.Decimals())) + minOut := out.Mul(decimal.NewFromInt(1).Sub(s.Config.Slippage)).Round(int32(tokenOut.Decimals())) - dexTraderGasFee, err := s.dexTrader.EstimateDexTradeGasFee(tokenIn, tokenOut, s.uniswapFee.BigInt(), qty, minOut) + dexTraderGasFee, err := s.DexTrader.EstimateDexTradeGasFee(tokenIn, tokenOut, s.UniswapFee.BigInt(), qty, minOut) if err != nil { // If the dexTrader's assets are less than the specified quantity, // the EstimateDexTradeGasFee function will return an "execution reverted: STF" error. @@ -229,14 +216,14 @@ func (s *SellDaiUniswapBuyTetherNobitex) findUniswapOrderCandidate(ctx context.C return oc, nil } -func (s *SellDaiUniswapBuyTetherNobitex) findNobitexOrderCandidate(ctx context.Context, qty decimal.Decimal) (*OrderCandidate, error) { +func (s *SellDaiUniswapBuyTetherNobitex) findNobitexOrderCandidate(qty decimal.Decimal) (*OrderCandidate, error) { // qty is in tether // makerFee := s.nobitex.Fees(trade.Maker) // minOrder := s.nobitex.MinimumOrderToman() - usdtPrice := s.marketsdata[Nobitex].Prices[symbol.USDT] - tomanBalance := s.marketsdata[Nobitex].Balances[symbol.RLS].Div(decimal.NewFromInt(10)) // convert from rial to toman + usdtPrice := s.Marketsdata[Nobitex].Prices[symbol.USDT] + tomanBalance := s.Marketsdata[Nobitex].Balances[symbol.RLS].Div(decimal.NewFromInt(10)) // convert from rial to toman - orderBook, err := s.nobitex.OrderBook(symbol.USDT, symbol.IRT) + orderBook, err := s.Nobitex.OrderBook(symbol.USDT, symbol.IRT) if err != nil { return nil, fmt.Errorf("failed to get nobitex order book: %w", err) } @@ -280,5 +267,5 @@ func (s *SellDaiUniswapBuyTetherNobitex) Teardown() { marketsdata := make(map[Market]MarketData) marketsdata[UniswapV3] = NewMarketData() marketsdata[Nobitex] = NewMarketData() - s.marketsdata = marketsdata + s.Marketsdata = marketsdata } diff --git a/internal/strategy/types.go b/internal/strategy/types.go index f660d65..369fe11 100644 --- a/internal/strategy/types.go +++ b/internal/strategy/types.go @@ -16,7 +16,6 @@ import ( type Config struct { StartQty decimal.Decimal StepQty decimal.Decimal - EndQty decimal.Decimal ProfitThreshold decimal.Decimal Slippage decimal.Decimal } diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go deleted file mode 100644 index a283fd8..0000000 --- a/pkg/logger/logger.go +++ /dev/null @@ -1,62 +0,0 @@ -package logger - -import ( - "context" - "io" - "os" - - "github.com/rs/zerolog" - "github.com/zarbanio/market-maker-keeper/store" -) - -var Logger zerolog.Logger - -type CustomWriter struct { - ctx context.Context - writer io.Writer - istore store.IStore -} - -func NewCustomWriter(ctx context.Context, istore store.IStore, writer io.Writer) *CustomWriter { - return &CustomWriter{ - ctx: ctx, - writer: writer, - istore: istore, - } -} - -func (cw *CustomWriter) Write(p []byte) (n int, err error) { - _, err = cw.istore.CreateLog(cw.ctx, p) - if err != nil { - return 0, err - } - - return cw.writer.Write(p) -} - -func InitLogger(ctx context.Context, s store.IStore, level string) error { - customWriter := NewCustomWriter(ctx, s, os.Stdout) - lvl := ParseLevel(level) - - Logger = zerolog.New(customWriter).With().Logger().Output(customWriter).Level(lvl) - return nil -} - -func ParseLevel(level string) zerolog.Level { - switch level { - case "debug": - return zerolog.DebugLevel - case "info": - return zerolog.InfoLevel - case "warn": - return zerolog.WarnLevel - case "error": - return zerolog.ErrorLevel - case "fatal": - return zerolog.FatalLevel - case "panic": - return zerolog.PanicLevel - default: - return zerolog.InfoLevel - } -}