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

feat/fix: add keyring; fix balance filtering; generalized concurrency #4

Open
wants to merge 4 commits into
base: roman/concurrent-pairs-api-key-sqs
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
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}"
"program": "${workspaceFolder}",
}
]
}
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@ Uses top-of-block auction.

## Setup

Create a `.env` file with the following content:
### Setup Keyring

```bash
GRPC_ADDRESS=http://localhost:26657
OSMOSIS_ACCOUNT_KEY=your_key_here
SQS_OSMOSIS_API_KEY=your_key_here
osmosisd keys add test --keyring-backend file --recover

# Enter your mnemonic

# Confirm creation
ls $HOME/.osmosisd/keyring-file

# Use result for OSMOSIS_KEYRING_PATH
```

### Setup .env

Create a `.env` file with the following content:
```bash
GRPC_ADDRESS=localhost:9090
OSMOSIS_KEYRING_PATH="/root/.osmosisd/keyring-file"
OSMOSIS_KEYRING_KEY_NAME=your_name
SQS_OSMOSIS_API_KEY=your_key
BINANCE_API_KEY=your_key
BINANCE_SECRET_KEY=yor_secret
```
go run main.go

```bash
go run main.go --password <your_keyring_password>
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/joho/godotenv v1.5.1
github.com/osmosis-labs/osmosis/v25 v25.0.3
github.com/skip-mev/block-sdk v1.4.2
go.uber.org/atomic v1.11.0
google.golang.org/grpc v1.63.2
)

Expand Down Expand Up @@ -187,7 +188,6 @@ 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
51 changes: 39 additions & 12 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"flag"
"fmt"
"log"
"time"
Expand All @@ -18,28 +19,52 @@ func main() {
log.Fatalf("Error loading .env file")
}

seedConfig, err := src.OsmosisInit()
keyringPassword := flag.String("password", "", "password for keyring")

flag.Parse()

seedConfig, err := src.OsmosisInit(*keyringPassword)
if err != nil {
fmt.Println(err)
panic(err)
}

pairs := []domain.OsmoBinanceArbPairMetadata{
{
BaseChainDenom: "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 6,
ExponentQuote: 6,
BaseChainDenom: "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 6,
ExponentQuote: 6,

BaseBinanceSymbol: "ATOM",
QuoteBinanceSymbol: "USDT",

BinancePairTicker: "ATOMUSDT",
RiskFactor: 0.99,
},
{
BaseChainDenom: "factory/osmo1z6r6qdknhgsc0zeracktgpcxf43j6sekq07nw8sxduc9lg0qjjlqfu25e3/alloyed/allBTC",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 8,
ExponentQuote: 6,
BaseChainDenom: "factory/osmo1z6r6qdknhgsc0zeracktgpcxf43j6sekq07nw8sxduc9lg0qjjlqfu25e3/alloyed/allBTC",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 8,
ExponentQuote: 6,

BaseBinanceSymbol: "BTC",
QuoteBinanceSymbol: "USDT",

BinancePairTicker: "BTCUSDT",
RiskFactor: 0.99,
},
{
BaseChainDenom: "ibc/D1542AA8762DB13087D8364F3EA6509FD6F009A34F00426AF9E4F9FA85CBBF1F",
QuoteChainDenom: "factory/osmo1em6xs47hd82806f5cxgyufguxrrc7l0aqx7nzzptjuqgswczk8csavdxek/alloyed/allUSDT",
ExponentBase: 8,
ExponentQuote: 6,

BaseBinanceSymbol: "WBTC",
QuoteBinanceSymbol: "USDT",

BinancePairTicker: "WBTCUSDT",
RiskFactor: 0.99,
},
}

for _, pair := range pairs {
Expand All @@ -52,13 +77,15 @@ func main() {

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

for {
// Execute the function
err := src.CheckArbitrage(seedConfig, arbPairMetaData)
fmt.Println(err)
if err != nil {
fmt.Println(err)
}

// Wait for the next tick
<-ticker.C
Expand Down
30 changes: 16 additions & 14 deletions src/arb.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ func CheckArbitrage(seedConfig SeedConfig, arbPairMetaData domain.OsmoBinanceArb

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

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

if binanceBasePrice < osmosisBTCPrice*arbPairMetaData.RiskFactor {

Expand All @@ -62,17 +62,17 @@ func CheckArbitrage(seedConfig SeedConfig, arbPairMetaData domain.OsmoBinanceArb
return err
}

_, _, err = BuyBinanceBTC(arbAmount)
_, _, err = BuyBinanceBase(arbAmount, arbPairMetaData.BinancePairTicker)
if err != nil {
return err
}

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

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

arbitrageOpCount.Add(1)

Expand All @@ -84,7 +84,7 @@ func CheckArbitrage(seedConfig SeedConfig, arbPairMetaData domain.OsmoBinanceArb
return err
}

_, _, err = SellBinanceBTC(arbAmount)
_, _, err = SellBinanceBase(arbAmount, arbPairMetaData.BinancePairTicker)
if err != nil {
return err
}
Expand All @@ -109,33 +109,35 @@ func CheckArbitrage(seedConfig SeedConfig, arbPairMetaData domain.OsmoBinanceArb

// for arb amount, we use 10% of the smaller asset we have between btc and usdt
// amount being returned is in units of btc
func calculateArbAmount(btcBalance, usdtBalance, btcPrice float64) (float64, error) {
func calculateArbAmount(baseBalance, quoteBalance, basePrice float64) (float64, error) {
const arbPercentage = 0.1

if btcBalance == 0 || usdtBalance == 0 {
if baseBalance == 0 || quoteBalance == 0 {
return 0, fmt.Errorf("insufficient balance for arbitrage")
}

// Calculate the BTC equivalent of the USDT balance
btcEquivalent := usdtBalance / btcPrice
baseEquivalent := quoteBalance / basePrice

fmt.Println("baseBalance: ", baseBalance, " baseEquivalent: ", baseEquivalent, " quoteBalance: ", quoteBalance, " basePrice: ", basePrice)

// Calculate the arbitrage amount based on the smaller balance in BTC units
arbAmount := arbPercentage * min(btcBalance, btcEquivalent)
arbAmount := arbPercentage * min(baseBalance, baseEquivalent)
return arbAmount, nil
}

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

osmosisBTCBalance, osmosisUSDTBalance, err := GetOsmosisBTCUSDTBalance(seedConfig, arbMetadata)
osmosisbaseBalance, osmosisquoteBalance, err := GetOsmosisBaseQuoteBalance(seedConfig, arbMetadata)
if err != nil {
return 0, 0, fmt.Errorf("error fetching Osmosis balance: %v", err)
return 0, 0, fmt.Errorf("error fetching Osmosis balance: %v, - %v", err, arbMetadata)
}

return binanceBTCBalance + osmosisBTCBalance, binanceUSDTBalance + osmosisUSDTBalance, nil
return binancebaseBalance + osmosisbaseBalance, binancequoteBalance + osmosisquoteBalance, nil
}

func getTime() string {
Expand Down
37 changes: 18 additions & 19 deletions src/binance.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strconv"

"github.com/adshao/go-binance/v2"
"github.com/osmosis-labs/arb-bot/src/domain"
)

type BinanceResponse struct {
Expand Down Expand Up @@ -43,19 +44,19 @@ func GetBinancePrice(ticker string) (float64, error) {
}

func GetBinanceInvertedPrice(ticker string) (float64, error) {
btcPrice, err := GetBinancePrice(ticker)
basePrice, err := GetBinancePrice(ticker)
if err != nil {
return 0, err
}

if btcPrice != 0 {
return 1 / btcPrice, nil
if basePrice != 0 {
return 1 / basePrice, nil
} else {
return 0, fmt.Errorf("BTC price from Binance is zero, cannot compute USDC to BTC price")
}
}

func GetBinanceBTCUSDTBalance() (btcBalance float64, usdtBalance float64, err error) {
func GetBinanceBaseQuoteBalance(arbMetadata domain.OsmoBinanceArbPairMetadata) (baseBalance float64, quoteBalance float64, err error) {
apiKey := os.Getenv("BINANCE_API_KEY")
secretKey := os.Getenv("BINANCE_SECRET_KEY")

Expand All @@ -66,33 +67,33 @@ func GetBinanceBTCUSDTBalance() (btcBalance float64, usdtBalance float64, err er
return 0, 0, err
}

filteredBalances := filterBalances(res.Balances, []string{"BTC", "USDT"})
filteredBalances := filterBalances(res.Balances, []string{arbMetadata.BaseBinanceSymbol, arbMetadata.QuoteBinanceSymbol})
for _, balance := range filteredBalances {
fmt.Printf("Asset: %s, Free: %s\n", balance.Asset, balance.Free)
}

btcBalance, err = strconv.ParseFloat(filteredBalances[0].Free, 64)
baseBalance, err = strconv.ParseFloat(filteredBalances[0].Free, 64)
if err != nil {
return 0, 0, err
}

usdtBalance, err = strconv.ParseFloat(filteredBalances[1].Free, 64)
quoteBalance, err = strconv.ParseFloat(filteredBalances[1].Free, 64)
if err != nil {
return 0, 0, err
}

return btcBalance, usdtBalance, nil
return baseBalance, quoteBalance, nil
}

func BuyBinanceBTC(amount float64) (boughtAmount, boughtPrice float64, err error) {
func BuyBinanceBase(amount float64, ticker string) (boughtAmount, boughtPrice float64, err error) {
apiKey := os.Getenv("BINANCE_API_KEY")
secretKey := os.Getenv("BINANCE_SECRET_KEY")
client := binance.NewClient(apiKey, secretKey)

amountStr := strconv.FormatFloat(amount, 'f', -1, 64)

// TODO: consider doing limit orders here
order, err := client.NewCreateOrderService().Symbol("BTCUSDT").Side(binance.SideTypeBuy).Type(binance.OrderTypeMarket).Quantity(amountStr).Do(context.Background())
order, err := client.NewCreateOrderService().Symbol(ticker).Side(binance.SideTypeBuy).Type(binance.OrderTypeMarket).Quantity(amountStr).Do(context.Background())
if err != nil {
return 0, 0, err
}
Expand All @@ -110,15 +111,15 @@ func BuyBinanceBTC(amount float64) (boughtAmount, boughtPrice float64, err error
return boughtAmount, boughtPrice, nil
}

func SellBinanceBTC(amount float64) (soldAmount, soldPrice float64, err error) {
func SellBinanceBase(amount float64, ticker string) (soldAmount, soldPrice float64, err error) {
apiKey := os.Getenv("BINANCE_API_KEY")
secretKey := os.Getenv("BINANCE_SECRET_KEY")
client := binance.NewClient(apiKey, secretKey)

amountStr := strconv.FormatFloat(amount, 'f', -1, 64)

// TODO: consider doing limit orders here
order, err := client.NewCreateOrderService().Symbol("BTCUSDT").Side(binance.SideTypeSell).Type(binance.OrderTypeMarket).Quantity(amountStr).Do(context.Background())
order, err := client.NewCreateOrderService().Symbol(ticker).Side(binance.SideTypeSell).Type(binance.OrderTypeMarket).Quantity(amountStr).Do(context.Background())
if err != nil {
return 0, 0, err
}
Expand All @@ -138,14 +139,12 @@ func SellBinanceBTC(amount float64) (soldAmount, soldPrice float64, err error) {

func filterBalances(balances []binance.Balance, assets []string) []binance.Balance {
var filtered []binance.Balance
assetSet := make(map[string]bool)
// We do a N^2 loop to preserve the order of the assets in the filtered list
for _, asset := range assets {
assetSet[asset] = true
}

for _, balance := range balances {
if assetSet[balance.Asset] {
filtered = append(filtered, balance)
for _, balance := range balances {
if balance.Asset == asset {
filtered = append(filtered, balance)
}
}
}
return filtered
Expand Down
3 changes: 3 additions & 0 deletions src/domain/arb_pair_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ type OsmoBinanceArbPairMetadata struct {
ExponentBase int
ExponentQuote int

BaseBinanceSymbol string
QuoteBinanceSymbol string

BinancePairTicker string

RiskFactor float64
Expand Down
Loading