Skip to content

Commit

Permalink
Merge pull request #6 from zarbanio/fix-strategy
Browse files Browse the repository at this point in the history
fix(execution): fix strategy execution
  • Loading branch information
amintalebi authored Oct 9, 2024
2 parents ba4a3cf + 869707f commit 9f9bedf
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 47 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ clean:
rm abis/uniswapv3_pool/uniswapv3_pool.go
rm abis/uniswapv3_factory/uniswapv3_factory.go
rm abis/uniswapv3_quoter/uniswapv3_quoter.go

run:
go build -ldflags=-checklinkname=0 -o main main.go
./main run --config configs/config.minimal.sample.yaml
2 changes: 1 addition & 1 deletion configs/config.all.sample.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
General:
LogLevel: "info"
LogLevel: "debug"

MarketMaker:
StartQty: 10
Expand Down
4 changes: 2 additions & 2 deletions configs/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ func DefaultConfig() Config {
LogLevel: "info",
},
MarketMaker: MarketMaker{
StartQty: 1,
StartQty: 5,
StepQty: 1,
ProfitThreshold: 5_000, // 50_000 TMN
Interval: time.Minute * 10,
Slippage: 0.001,
Slippage: 0.005,
},
Chain: Chain{
BlockInterval: time.Millisecond * 500,
Expand Down
4 changes: 4 additions & 0 deletions internal/cmd/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ func main(cfg configs.Config) {
if err != nil {
logger.Panic().Err(err).Msg("error while initializing new executor wallet")
}
logger.Info().
Str("address", executorWallet.Address().Hex()).
Msg("executor wallet initialized")
eth, err := ethclient.Dial(cfg.Chain.Url)
if err != nil {
logger.Panic().Err(err).Msg("error while dialing eth client")
Expand Down Expand Up @@ -212,6 +215,7 @@ func main(cfg configs.Config) {
Logger: logger,
NobitexRetryTimeOut: cfg.Nobitex.RetryTimeOut,
NobitexSleepDuration: cfg.Nobitex.RetrySleepDuration,
UniswapFee: domain.UniswapFeeFee01,
}

ticker := time.NewTicker(cfg.MarketMaker.Interval)
Expand Down
29 changes: 16 additions & 13 deletions internal/executor/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package executor
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"

Expand Down Expand Up @@ -43,39 +44,41 @@ func (e *Executor) RunAll() {
}
}

func (e *Executor) Run(strategy strategy.ArbitrageStrategy) error {
marketdata, err := strategy.Setup()
func (e *Executor) Run(strg strategy.ArbitrageStrategy) error {
marketdata, err := strg.Setup()
if err != nil {
return fmt.Errorf("failed to setup strategy %s. %w", strategy.Name(), err)
return fmt.Errorf("failed to setup strategy %s. %w", strg.Name(), err)
}
e.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", strg.Name()).Object("marketdata", marketdata).Msg("strategy setup completed.")

opportunity, err := strategy.Evaluate(context.Background())
opportunity, err := strg.Evaluate(context.Background())
if err != nil {
return fmt.Errorf("failed to evaluate strategy %s. %w", strategy.Name(), err)
if !errors.As(err, &strategy.ErrorInsufficentBalance{}) {
return fmt.Errorf("failed to evaluate strategy %s. %w", strg.Name(), err)
}
}
if opportunity == nil {
e.Logger.Info().
Int64("cycleId", e.CycleId).
Str("strategy", strategy.Name()).
Str("strategy", strg.Name()).
Msg("no profitable opportunity found.")
return nil
}

e.Logger.Info().
Int64("cycleId", e.CycleId).
Str("strategy", strategy.Name()).
Str("strategy", strg.Name()).
Object("bestArbirageOpportunity", opportunity).
Msg("a profitable opportunity found.")

err = e.Execute(strategy.Name(), opportunity.NobitexOrderCandidate)
err = e.Execute(strg.Name(), opportunity.NobitexOrderCandidate)
if err != nil {
return fmt.Errorf("failed to execute strategy %s. %w", strategy.Name(), err)
return fmt.Errorf("failed to execute strategy %s. %w", strg.Name(), err)
}

err = e.Execute(strategy.Name(), opportunity.UniV3OrderCandidate)
err = e.Execute(strg.Name(), opportunity.UniV3OrderCandidate)
if err != nil {
return fmt.Errorf("failed to execute strategy %s. %w", strategy.Name(), err)
return fmt.Errorf("failed to execute strategy %s. %w", strg.Name(), err)
}

_, err = e.Store.CreateNewTrade(context.Background(), e.PairId, e.OrderId, e.TransactionId)
Expand All @@ -93,7 +96,7 @@ func (e *Executor) Run(strategy strategy.ArbitrageStrategy) error {
return err
}

strategy.Teardown()
strg.Teardown()

return nil
}
Expand Down
5 changes: 5 additions & 0 deletions internal/keystore/hdwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keystore
import (
"crypto/ecdsa"
"log"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
Expand All @@ -15,6 +16,10 @@ type KeyStore struct {
}

func New(key string) (*KeyStore, error) {
ok := strings.HasPrefix(key, "0x")
if !ok {
key = "0x" + key
}
privateKeyBytes, err := hexutil.Decode(key)
if err != nil {
log.Fatalf("Failed to decode private key: %v", err)
Expand Down
1 change: 0 additions & 1 deletion internal/nobitex/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ func (c *httpClient) placeOrder(order order.Order) (placeOrderResponse, error) {
"amount": amount,
"price": price,
})
fmt.Println(string(body))
if err != nil {
return placeOrderResponse{}, err
}
Expand Down
8 changes: 4 additions & 4 deletions internal/nobitex/nobitex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@ func TestPlaceOrder(t *testing.T) {
Price string `json:"price"`
Amount string `json:"amount"`
TotalPrice string `json:"totalPrice"`
MatchedAmount int `json:"matchedAmount"`
MatchedAmount string `json:"matchedAmount"`
UnmatchedAmount string `json:"unmatchedAmount"`
Id int `json:"id"`
Status string `json:"status"`
Partial bool `json:"partial"`
Fee int `json:"fee"`
Fee string `json:"fee"`
User string `json:"user"`
CreatedAt time.Time `json:"created_at"`
}{
Expand All @@ -101,12 +101,12 @@ func TestPlaceOrder(t *testing.T) {
Price: "50000",
Amount: "1",
TotalPrice: "50000",
MatchedAmount: 1,
MatchedAmount: "1",
UnmatchedAmount: "0",
Id: 123,
Status: "open",
Partial: false,
Fee: 100,
Fee: "100",
User: "johndoe",
CreatedAt: time.Now(),
},
Expand Down
4 changes: 2 additions & 2 deletions internal/nobitex/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ type (
Price string `json:"price"`
Amount string `json:"amount"`
TotalPrice string `json:"totalPrice"`
MatchedAmount int `json:"matchedAmount"`
MatchedAmount string `json:"matchedAmount"`
UnmatchedAmount string `json:"unmatchedAmount"`
Id int `json:"id"`
Status string `json:"status"`
Partial bool `json:"partial"`
Fee int `json:"fee"`
Fee string `json:"fee"`
User string `json:"user"`
CreatedAt time.Time `json:"created_at"`
} `json:"order"`
Expand Down
49 changes: 36 additions & 13 deletions internal/strategy/buy_uniswap_sell_nobitex.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ func (s *BuyDaiUniswapSellTetherNobitex) getNobitexBalances() (map[symbol.Symbol
return nil, fmt.Errorf("failed to get nobitex balances. %w", err)
}
for _, balance := range balances {
if balance.Balance.IsZero() {
continue
}
s.Marketsdata[Nobitex].Balances[balance.Symbol] = balance.Balance
}
return s.Marketsdata[Nobitex].Balances, nil
Expand Down Expand Up @@ -106,26 +109,26 @@ func (s *BuyDaiUniswapSellTetherNobitex) getNobitexPrices() (map[symbol.Symbol]d

func (s *BuyDaiUniswapSellTetherNobitex) Evaluate(ctx context.Context) (*ArbitrageOpportunity, error) {
startQty := s.Config.StartQty // in dai
endQty := decimal.Min(
s.Marketsdata[Nobitex].Balances[symbol.USDT],
s.Marketsdata[UniswapV3].Balances[symbol.DAI],
)
endQty := s.Marketsdata[Nobitex].Balances[symbol.USDT]
stepQty := s.Config.StepQty // in dai

s.Logger.Debug().
Str("startQty", domain.CommaSeparate(startQty.String())).
Str("endQty", domain.CommaSeparate(endQty.String())).
Str("stepQty", domain.CommaSeparate(stepQty.String())).
Str("strategy", s.Name()).
Msg("evaluating arbitrage opportunity")

var bestArbirageOpportunity *ArbitrageOpportunity

for qty := startQty; qty.LessThanOrEqual(endQty); qty = qty.Add(stepQty) {
uniswapV3OrderCandidate, err := s.findUniswapOrderCandidate(ctx, qty)
if err != nil {
if errors.Is(err, ErrorInsufficentBalance{}) {
break
}
return nil, err
}
s.Logger.Debug().
Str("qty", domain.CommaSeparate(qty.String())).
Msg("evaluating arbitrage opportunity")

nobitexOrderCandidate, err := s.findNobitexOrderCandidate(ctx, qty)
if err != nil {
if errors.Is(err, ErrorInsufficentBalance{}) {
if errors.As(err, &ErrorInsufficentBalance{}) {
break
}
if errors.Is(err, ErrorInvalidAmount) {
Expand All @@ -135,14 +138,34 @@ func (s *BuyDaiUniswapSellTetherNobitex) Evaluate(ctx context.Context) (*Arbitra
return nil, err
}

s.Logger.Debug().
Object("nobitexOrderCandidate", nobitexOrderCandidate).
Msg("nobitex order candidate found")

uniswapV3OrderCandidate, err := s.findUniswapOrderCandidate(ctx, qty)
if err != nil {
if errors.As(err, &ErrorInsufficentBalance{}) {
break
}
return nil, err
}

s.Logger.Debug().
Object("uniswapV3OrderCandidate", uniswapV3OrderCandidate).
Msg("uniswap v3 order candidate found")

arbitrageOpportunity := &ArbitrageOpportunity{
UniV3OrderCandidate: *uniswapV3OrderCandidate,
NobitexOrderCandidate: *nobitexOrderCandidate,
}
s.Logger.Debug().Object("ArbitrageOpportunity", arbitrageOpportunity).Msg("arbitrage opportunity")
s.Logger.Debug().Object("arbitrageOpportunity", arbitrageOpportunity).Msg("arbitrage opportunity")

if arbitrageOpportunity.EstimatedProfit().GreaterThan(bestArbirageOpportunity.EstimatedProfit()) {
bestArbirageOpportunity = arbitrageOpportunity

s.Logger.Debug().
Object("arbitrageOpportunity", bestArbirageOpportunity).
Msg("best arbitrage opportunity so far")
}
}

Expand Down
37 changes: 29 additions & 8 deletions internal/strategy/sell_uniswap_buy_nobitex.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ func (s *SellDaiUniswapBuyTetherNobitex) getNobitexBalances() (map[symbol.Symbol
return nil, fmt.Errorf("failed to get nobitex balances. %w", err)
}
for _, balance := range balances {
if balance.Balance.IsZero() {
continue
}
s.Marketsdata[Nobitex].Balances[balance.Symbol] = balance.Balance
}
return s.Marketsdata[Nobitex].Balances, nil
Expand Down Expand Up @@ -94,28 +97,38 @@ func (s *SellDaiUniswapBuyTetherNobitex) Evaluate(ctx context.Context) (*Arbitra
}

startQty := s.Config.StartQty
endQty := decimal.Min(
s.Marketsdata[Nobitex].Balances[symbol.USDT],
s.Marketsdata[UniswapV3].Balances[symbol.DAI],
)
endQty := s.Marketsdata[UniswapV3].Balances[symbol.DAI]
stepQty := s.Config.StepQty

var bestArbirageOpportunity *ArbitrageOpportunity

s.Logger.Debug().
Str("startQty", domain.CommaSeparate(startQty.String())).
Str("endQty", domain.CommaSeparate(endQty.String())).
Str("stepQty", domain.CommaSeparate(stepQty.String())).
Str("strategy", s.Name()).
Msg("evaluating arbitrage opportunity")

for qty := startQty; qty.LessThanOrEqual(endQty); qty = qty.Add(stepQty) {
s.Logger.Debug().
Str("qty", domain.CommaSeparate(qty.String())).
Msg("evaluating arbitrage opportunity")

uniswapV3OrderCandidate, err := s.findUniswapOrderCandidate(ctx, qty)
if err != nil {
if errors.Is(err, ErrorInsufficentBalance{}) {
s.Logger.Warn().Err(err).Msg("not enough balance to find the best arbitrage opportunity.")
if errors.As(err, &ErrorInsufficentBalance{}) {
break
}
return nil, fmt.Errorf("failed to find uniswap order candidate. %w", err)
}

s.Logger.Debug().
Object("uniswapV3OrderCandidate", uniswapV3OrderCandidate).
Msg("uniswap v3 order candidate found")

nobitexOrderCandidate, err := s.findNobitexOrderCandidate(qty)
if err != nil {
if errors.Is(err, ErrorInsufficentBalance{}) {
s.Logger.Warn().Err(err).Msg("not enough balance to find the best arbitrage opportunity.")
if errors.As(err, &ErrorInsufficentBalance{}) {
break
}
if errors.Is(err, ErrorInvalidAmount) {
Expand All @@ -125,6 +138,10 @@ func (s *SellDaiUniswapBuyTetherNobitex) Evaluate(ctx context.Context) (*Arbitra
return nil, fmt.Errorf("failed to find nobitex order candidate. %w", err)
}

s.Logger.Debug().
Object("nobitexOrderCandidate", nobitexOrderCandidate).
Msg("nobitex order candidate found")

arbitrageOpportunity := &ArbitrageOpportunity{
UniV3OrderCandidate: *uniswapV3OrderCandidate,
NobitexOrderCandidate: *nobitexOrderCandidate,
Expand All @@ -134,6 +151,10 @@ func (s *SellDaiUniswapBuyTetherNobitex) Evaluate(ctx context.Context) (*Arbitra

if arbitrageOpportunity.EstimatedProfit().GreaterThan(bestArbirageOpportunity.EstimatedProfit()) {
bestArbirageOpportunity = arbitrageOpportunity

s.Logger.Debug().
Object("arbitrageOpportunity", bestArbirageOpportunity).
Msg("best arbitrage opportunity so far")
}
}

Expand Down
2 changes: 0 additions & 2 deletions store/order_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package store

import (
"context"
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -95,7 +94,6 @@ func TestUpdateOrder(t *testing.T) {

newID, err := psql.CreateNewOrder(context.Background(), *newOrder)
require.NoError(t, err)
fmt.Printf("newID: %v\n", newID)
matchedAmount := decimal.NewFromInt(0)
unmatchedAmount := decimal.NewFromInt(0)
status := order.Canceled
Expand Down
1 change: 0 additions & 1 deletion store/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ func TestUpdateTransaction(t *testing.T) {

newID, err := p.CreateTransaction(context.Background(), dummyTransaction, *dummyTransaction.To())
require.NoError(t, err)
fmt.Printf("newID: %v\n", newID)

// Create the updated fields
updatedFields := transaction.UpdatedFields{
Expand Down

0 comments on commit 9f9bedf

Please sign in to comment.