Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: support multiple concurrent arb pairs & SQS API key #3

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Create a `.env` file with the following content:
```bash
GRPC_ADDRESS=http://localhost:26657
OSMOSIS_ACCOUNT_KEY=your_key_here
SQS_OSMOSIS_API_KEY=your_key_here
```

```
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ require (
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1776,6 +1776,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
Expand Down
36 changes: 30 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/joho/godotenv"

"github.com/osmosis-labs/arb-bot/src"
"github.com/osmosis-labs/arb-bot/src/domain"
)

func main() {
Expand All @@ -22,21 +23,44 @@ func main() {
fmt.Println(err)
}

pairs := []domain.OsmoBinanceArbPairMetadata{
{
BaseChainDenom: "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 6,
ExponentQuote: 6,
BinancePairTicker: "ATOMUSDT",
RiskFactor: 0.99,
},
{
BaseChainDenom: "factory/osmo1z6r6qdknhgsc0zeracktgpcxf43j6sekq07nw8sxduc9lg0qjjlqfu25e3/alloyed/allBTC",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 8,
ExponentQuote: 6,
BinancePairTicker: "BTCUSDT",
RiskFactor: 0.99,
},
}

for _, pair := range pairs {
go runArbitrageCheck(seedConfig, pair)
}

// Keep the program running
select {}
}

func runArbitrageCheck(seedConfig src.SeedConfig, arbPairMetaData domain.OsmoBinanceArbPairMetadata) {
// Set up a ticker to run the function every minute
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()

for {
// Execute the function
err = runArbitrageCheck(seedConfig)
err := src.CheckArbitrage(seedConfig, arbPairMetaData)
fmt.Println(err)

// Wait for the next tick
<-ticker.C
}
}

func runArbitrageCheck(seedConfig src.SeedConfig) error {
err := src.CheckArbitrage(seedConfig)
return err
}
64 changes: 42 additions & 22 deletions src/arb.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,62 @@ package src

import (
"fmt"
"sync"
"time"

"github.com/osmosis-labs/arb-bot/src/domain"
"go.uber.org/atomic"
)

var (
arbitrageOpCount *atomic.Int64 = atomic.NewInt64(0)
arbLock sync.Mutex
)

func CheckArbitrage(seedConfig SeedConfig) error {
func CheckArbitrage(seedConfig SeedConfig, arbPairMetaData domain.OsmoBinanceArbPairMetadata) error {
// Note: this is for prints to look nice in console, we can optimize this in the future.
arbLock.Lock()
defer arbLock.Unlock()

time := getTime()
fmt.Println("=======Starting ARB in ", time, "=======")

btcBalance, usdtBalance, err := GetTotalBalance(seedConfig)
baseBalance, quoteBalance, err := GetTotalBalance(seedConfig, arbPairMetaData)
if err != nil {
return err
}

fmt.Println("Balance before arb is, btc: ", btcBalance, " usdt: ", usdtBalance)
fmt.Println("Balance before arb is, base: ", baseBalance, " quote: ", quoteBalance)

binanceBTCPrice, err := GetBinanceBTCToUSDTPrice()
binanceBasePrice, err := GetBinancePrice(arbPairMetaData.BinancePairTicker)
if err != nil {
return fmt.Errorf("error fetching Binance BTC price: %v", err)
return fmt.Errorf("error fetching Binance price: %v", err)
}

arbAmount, err := calculateArbAmount(btcBalance, usdtBalance, binanceBTCPrice)
arbAmount, err := calculateArbAmount(baseBalance, quoteBalance, binanceBasePrice)
if err != nil {
return err
}
fmt.Println("Binance BTC Price:", binanceBTCPrice)
fmt.Println("Binance base Price:", binanceBasePrice)

osmosisBTCPrice, route, err := GetOsmosisBTCToUSDCPriceAndRoute(arbAmount)
osmosisBTCPrice, route, err := GetOsmosisBTCToUSDCPriceAndRoute(arbAmount, arbPairMetaData.BaseChainDenom, arbPairMetaData.QuoteChainDenom, arbPairMetaData.ExponentBase, arbPairMetaData.ExponentQuote)
if err != nil {
return fmt.Errorf("error fetching Osmosis BTC price: %v", err)
return fmt.Errorf("error fetching Osmosis base price: %v", err)
}

fmt.Println("Osmosis BTC Price:", osmosisBTCPrice)

if binanceBTCPrice < osmosisBTCPrice*riskFactor {
fmt.Println("Arbitrage Opportunity: Buy BTC on Binance, Sell BTC on Osmosis")
if binanceBasePrice < osmosisBTCPrice*arbPairMetaData.RiskFactor {

_, route, err := GetOsmosisUSDCToBTCPriceAndRoute(arbAmount)
fmt.Println("Arbitrage Opportunity: Buy base on Binance, Sell base on Osmosis")

_, route, err := GetOsmosisUSDCToBTCPriceAndRoute(arbAmount, arbPairMetaData.BaseChainDenom, arbPairMetaData.QuoteChainDenom, arbPairMetaData.ExponentBase, arbPairMetaData.ExponentQuote)

if err != nil {
return err
}

err = SellOsmosisBTC(seedConfig, route, binanceBTCPrice)
err = SellOsmosisBase(seedConfig, arbPairMetaData.BaseChainDenom, route, binanceBasePrice)
if err != nil {
return err
}
Expand All @@ -53,17 +67,19 @@ func CheckArbitrage(seedConfig SeedConfig) error {
return err
}

btcBalance, usdtBalance, err := GetTotalBalance(seedConfig)
btcBalance, usdtBalance, err := GetTotalBalance(seedConfig, arbPairMetaData)
if err != nil {
return err
}

fmt.Println("Balance after arb is, btc: ", btcBalance, " usdt: ", usdtBalance)
fmt.Println("Balance after arb is, base: ", btcBalance, " quote: ", usdtBalance)

arbitrageOpCount.Add(1)

} else if binanceBTCPrice*riskFactor > osmosisBTCPrice {
fmt.Println("Arbitrage Opportunity: Sell BTC on Binance, Buy BTC on Osmosis")
} else if binanceBasePrice*arbPairMetaData.RiskFactor > osmosisBTCPrice {
fmt.Println("Arbitrage Opportunity: Sell base on Binance, Buy base on Osmosis")

err = BuyOsmosisBTC(seedConfig, route, binanceBTCPrice)
err = BuyOsmosisBase(seedConfig, arbPairMetaData.BaseChainDenom, route, binanceBasePrice)
if err != nil {
return err
}
Expand All @@ -73,17 +89,21 @@ func CheckArbitrage(seedConfig SeedConfig) error {
return err
}

btcBalance, usdtBalance, err := GetTotalBalance(seedConfig)
baseBalance, quoteBalance, err := GetTotalBalance(seedConfig, arbPairMetaData)
if err != nil {
return err
}

fmt.Println("Balance after arb is, btc: ", btcBalance, " usdt: ", usdtBalance)
fmt.Println("Balance after arb is, base: ", baseBalance, " quote: ", quoteBalance)

arbitrageOpCount.Add(1)

} else {
fmt.Println("No arb opportunity")
}

fmt.Println("arb count: ", arbitrageOpCount)

return nil
}

Expand All @@ -104,13 +124,13 @@ func calculateArbAmount(btcBalance, usdtBalance, btcPrice float64) (float64, err
return arbAmount, nil
}

func GetTotalBalance(seedConfig SeedConfig) (float64, float64, error) {
func GetTotalBalance(seedConfig SeedConfig, arbMetadata domain.OsmoBinanceArbPairMetadata) (float64, float64, error) {
binanceBTCBalance, binanceUSDTBalance, err := GetBinanceBTCUSDTBalance()
if err != nil {
return 0, 0, fmt.Errorf("error fetching Binance balance: %v", err)
}

osmosisBTCBalance, osmosisUSDTBalance, err := GetOsmosisBTCUSDTBalance(seedConfig)
osmosisBTCBalance, osmosisUSDTBalance, err := GetOsmosisBTCUSDTBalance(seedConfig, arbMetadata)
if err != nil {
return 0, 0, fmt.Errorf("error fetching Osmosis balance: %v", err)
}
Expand Down
8 changes: 4 additions & 4 deletions src/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ type BinanceResponse struct {
Price string `json:"price"`
}

func GetBinanceBTCToUSDTPrice() (float64, error) {
url := fmt.Sprintf("https://api.binance.com/api/v3/ticker/price?symbol=%s", binanceBTCUSDTTicker)
func GetBinancePrice(ticker string) (float64, error) {
url := fmt.Sprintf("https://api.binance.com/api/v3/ticker/price?symbol=%s", ticker)
resp, err := http.Get(url)
if err != nil {
return 0, fmt.Errorf("error fetching price from Binance: %v", err)
Expand All @@ -42,8 +42,8 @@ func GetBinanceBTCToUSDTPrice() (float64, error) {
return price, nil
}

func GetBinanceUSDCToBTCPrice() (float64, error) {
btcPrice, err := GetBinanceBTCToUSDTPrice()
func GetBinanceInvertedPrice(ticker string) (float64, error) {
btcPrice, err := GetBinancePrice(ticker)
if err != nil {
return 0, err
}
Expand Down
14 changes: 1 addition & 13 deletions src/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,5 @@ package src

const (
osmosisQuoteAPI = "https://sqs.osmosis.zone/router/quote"
osmosisRouteAPI = "https://sqs.osmosis.zone/router/routes"

BTCDenom = "factory/osmo1z0qrq605sjgcqpylfl4aa6s90x738j7m58wyatt0tdzflg2ha26q67k743/wbtc"
USDCDenom = "ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4"
BidDenom = "stake"

binanceBTCUSDTTicker = "BTCUSDT"

defaultArbAmt = 0.0001
riskFactor = 0.98

osmosisWBTCExponent = 8
osmosisUSDCExponent = 6
BidDenom = "uosmo"
)
13 changes: 13 additions & 0 deletions src/domain/arb_pair_metadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package domain

// OsmoBinanceArbPairMetadata is a struct that holds the metadata for a pair of tokens that we want to arbitrage between
type OsmoBinanceArbPairMetadata struct {
BaseChainDenom string
QuoteChainDenom string
ExponentBase int
ExponentQuote int

BinancePairTicker string

RiskFactor float64
}
Loading