diff --git a/getters.go b/getters.go index 4a585e8..ddf35cc 100644 --- a/getters.go +++ b/getters.go @@ -9,7 +9,12 @@ import ( "strconv" ) -type TickerGetter func(string) (float64, error) +type AskBid struct { + Ask float64 + Bid float64 +} + +type TickerGetter func(string) (*AskBid, error) func getHTTPResponseBodyFromUrl(url string) ([]byte, error) { if debug { @@ -30,6 +35,27 @@ func getHTTPResponseBodyFromUrl(url string) ([]byte, error) { return body, nil } +type CoinmateTicker struct { + Data struct { + Ask float64 `json:"ask"` + Bid float64 `json:"bid"` + } `json:"data"` +} + +func CoinmateGetter(ticker string) (*AskBid, error) { + url := "https://coinmate.io/api/ticker?currencyPair=" + ticker + body, err := getHTTPResponseBodyFromUrl(url) + if err != nil { + return nil, err + } + var tickerData CoinmateTicker + err = json.Unmarshal(body, &tickerData) + if err != nil { + return nil, err + } + return &AskBid{tickerData.Data.Ask, tickerData.Data.Bid}, nil +} + type CoinbaseTicker struct { Price string `json:"price"` Ask string `json:"ask"` @@ -39,53 +65,53 @@ type CoinbaseTicker struct { } // ticker might be BTC-USD{T} -func CoinbaseGetter(ticker string) (float64, error) { +func CoinbaseGetter(ticker string) (*AskBid, error) { url := "https://api.pro.coinbase.com/products/" + ticker + "/ticker" body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData CoinbaseTicker err = json.Unmarshal(body, &tickerData) //fmt.Printf("Coinbase %s %#v\n", ticker, tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Ask b := tickerData.Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } -func GeminiGetter(ticker string) (float64, error) { +func GeminiGetter(ticker string) (*AskBid, error) { url := "https://api.gemini.com/v1/pubticker/" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData CoinbaseTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Ask b := tickerData.Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } type BybitTicker struct { @@ -97,29 +123,29 @@ type BybitTicker struct { } `json:"result"` } -func BybitGetter(ticker string) (float64, error) { +func BybitGetter(ticker string) (*AskBid, error) { url := "https://api-testnet.bybit.com/v5/market/tickers?category=linear&symbol=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData BybitTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Reusult.List[0].Ask b := tickerData.Reusult.List[0].Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } type BitstampTicker struct { @@ -127,30 +153,30 @@ type BitstampTicker struct { Bid string `json:"bid"` } -func BitstampGetter(ticker string) (float64, error) { +func BitstampGetter(ticker string) (*AskBid, error) { url := "https://www.bitstamp.net/api/v2/ticker/" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } //fmt.Printf("Bitstamp %s %s\n", ticker, string(body)) //fmt.Println(url) var tickerData BitstampTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Ask b := tickerData.Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } type KrakenTicker struct { @@ -160,28 +186,28 @@ type KrakenTicker struct { } `json:"result"` } -func KrakenGetter(ticker string) (float64, error) { +func KrakenGetter(ticker string) (*AskBid, error) { url := "https://api.kraken.com/0/public/Ticker?pair=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData KrakenTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Result[ticker].A[0] b := tickerData.Result[ticker].B[0] af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } type HuobiTicker struct { @@ -191,38 +217,38 @@ type HuobiTicker struct { } `json:"tick"` } -func HuobiGetter(ticker string) (float64, error) { +func HuobiGetter(ticker string) (*AskBid, error) { url := "https://api.huobi.pro/market/detail/merged?symbol=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData HuobiTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Tick.Ask[0] b := tickerData.Tick.Bid[0] - return (a + b) / 2, nil + return &AskBid{a, b}, nil } -func BinanceGetter(ticker string) (float64, error) { +func BinanceGetter(ticker string) (*AskBid, error) { url := "https://api1.binance.com/api/v3/ticker/price?symbol=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData CoinbaseTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } price, err := strconv.ParseFloat(tickerData.Price, 64) if err != nil { - return 0, err + return nil, err } - return price, nil + return &AskBid{price, price}, nil } type GateIOTicker struct { @@ -230,50 +256,50 @@ type GateIOTicker struct { Ask string `json:"lowest_ask"` } -func GateIOGetter(ticker string) (float64, error) { +func GateIOGetter(ticker string) (*AskBid, error) { url := "https://api.gateio.ws/api/v4/spot/tickers?currency_pair=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData []GateIOTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData[0].Ask b := tickerData[0].Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } type BitfinexTicker struct { Mid string `json:"mid"` } -func BitfinexGetter(ticker string) (float64, error) { +func BitfinexGetter(ticker string) (*AskBid, error) { url := "https://api.bitfinex.com/v1/pubticker/" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData BitfinexTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } price, err := strconv.ParseFloat(tickerData.Mid, 64) if err != nil { - return 0, err + return nil, err } - return price, nil + return &AskBid{price, price}, nil } type KUCoinTicker struct { @@ -284,28 +310,28 @@ type KUCoinTicker struct { } `json:"data"` } -func KUCoinGetter(ticker string) (float64, error) { +func KUCoinGetter(ticker string) (*AskBid, error) { url := "https://api.kucoin.com/api/v1/market/orderbook/level1?symbol=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData KUCoinTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Data.Ask b := tickerData.Data.Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } type BitflyerTicker struct { @@ -313,18 +339,18 @@ type BitflyerTicker struct { Bid float64 `json:"best_bid"` } -func BitflyerGetter(ticker string) (float64, error) { +func BitflyerGetter(ticker string) (*AskBid, error) { url := "https://api.bitflyer.com/v1/ticker?product_code=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData BitflyerTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } - return (tickerData.Ask + tickerData.Bid) / 2, nil + return &AskBid{tickerData.Ask, tickerData.Bid}, nil } type OkxTicker struct { @@ -334,26 +360,26 @@ type OkxTicker struct { } `json:"data"` } -func OkxGetter(ticker string) (float64, error) { +func OkxGetter(ticker string) (*AskBid, error) { url := "https://www.okx.com/api/v5/market/ticker?instId=" + ticker body, err := getHTTPResponseBodyFromUrl(url) if err != nil { - return 0, err + return nil, err } var tickerData OkxTicker err = json.Unmarshal(body, &tickerData) if err != nil { - return 0, err + return nil, err } a := tickerData.Data[0].Ask b := tickerData.Data[0].Bid af, err := strconv.ParseFloat(a, 64) if err != nil { - return 0, err + return nil, err } bf, err := strconv.ParseFloat(b, 64) if err != nil { - return 0, err + return nil, err } - return (af + bf) / 2, nil + return &AskBid{af, bf}, nil } diff --git a/main.go b/main.go index a30cf59..d2dfaf9 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,48 @@ import ( "github.com/t0mk/pri/symbols" ) +const ( + Coinmate Exchange = "coinmate" + Coinbase Exchange = "coinbase" + Binance Exchange = "binance" + Kraken Exchange = "kraken" + Bitstamp Exchange = "bitstamp" + Huobi Exchange = "huobi" + Kucoin Exchange = "kucoin" + Gateio Exchange = "gateio" + Bitfinex Exchange = "bitfinex" + Bybit Exchange = "bybit" + Okx Exchange = "okx" +) + +var exchangeGetters = map[Exchange]TickerGetter{ + Coinmate: CoinmateGetter, + Coinbase: CoinbaseGetter, + Binance: BinanceGetter, + Kraken: KrakenGetter, + Bitstamp: BitstampGetter, + Huobi: HuobiGetter, + Kucoin: KUCoinGetter, + Gateio: GateIOGetter, + Bitfinex: BitfinexGetter, + Bybit: BybitGetter, + Okx: OkxGetter, +} + +var exchangeSymbols = map[Exchange][]string{ + Coinmate: symbols.Coinmate, + Coinbase: symbols.Coinbase, + Binance: symbols.Binance, + Kraken: symbols.Kraken, + Bitstamp: symbols.Bitstamp, + Huobi: symbols.Huobi, + Kucoin: symbols.Kucoin, + Gateio: symbols.Gateio, + Bitfinex: symbols.Bitfinex, + Bybit: symbols.Bybit, + Okx: symbols.Okx, +} + var debug = false type Exchange string @@ -23,23 +65,38 @@ type ExTickSet struct { ExTicks []ExTick } +type TickPrice struct { + MinAsk float64 + MaxBid float64 +} + type ExTickPri struct { ExTick - Price float64 + Price TickPrice } type ExTickPris []ExTickPri -func (etps ExTickPris) Min() ExTickPri { +func (etps ExTickPris) MinAsk() ExTickPri { min := etps[0] for _, v := range etps { - if v.Price < min.Price { + if v.Price.MinAsk < min.Price.MinAsk { min = v } } return min } +func (etps ExTickPris) MaxBid() ExTickPri { + max := etps[0] + for _, v := range etps { + if v.Price.MaxBid > max.Price.MaxBid { + max = v + } + } + return max +} + func (etps ExTickPris) String() string { str := "" for _, v := range etps { @@ -52,8 +109,8 @@ func ExTickPrisToArbResult(n string, etps ExTickPris) ArbResult { return ArbResult{ Name: n, TickPris: etps, - Min: etps.Min(), - Max: etps.Max(), + MinAsk: etps.MinAsk(), + MaxBid: etps.MaxBid(), SpreadPercent: SpreadType(etps.SpreadPercent()), } } @@ -67,13 +124,13 @@ func (st SpreadType) String() string { type ArbResult struct { Name string TickPris ExTickPris - Min ExTickPri - Max ExTickPri + MinAsk ExTickPri + MaxBid ExTickPri SpreadPercent SpreadType } func (ar ArbResult) String() string { - return fmt.Sprintf("name: %s\nmin: %s\nmax: %s\nspread: %s", ar.Name, ar.Min, ar.Max, ar.SpreadPercent) + return fmt.Sprintf("name: %s\nmin: %s\nmax: %s\nspread: %s", ar.Name, ar.MinAsk, ar.MaxBid, ar.SpreadPercent) } type ArbResults []ArbResult @@ -108,69 +165,33 @@ func (ars ArbResults) Report() { fmt.Println(ars.LowestSpread()) } -func (etps ExTickPris) Max() ExTickPri { - max := etps[0] - for _, v := range etps { - if v.Price > max.Price { - max = v - } - } - return max -} - func (etps ExTickPris) SpreadPercent() float64 { - min := etps.Min() - max := etps.Max() - return (max.Price - min.Price) / min.Price * 100 + min := etps.MinAsk().Price.MinAsk + max := etps.MaxBid().Price.MaxBid + return (max - min) / min * 100 } func (etp ExTickPri) String() string { et := etp.ExTick - return fmt.Sprintf("[%15s]\t%s", et, formatFloat(etp.Price)) + return fmt.Sprintf("[%15s]\t[%15s - %15s]", et, formatFloat(etp.Price.MinAsk), formatFloat(etp.Price.MaxBid)) } func (et ExTick) String() string { return fmt.Sprintf("%s-%s", et.Exchange, et.Ticker) } -const ( - Coinbase Exchange = "coinbase" - Binance Exchange = "binance" - Kraken Exchange = "kraken" - Bitstamp Exchange = "bitstamp" - Huobi Exchange = "huobi" - Kucoin Exchange = "kucoin" - Gateio Exchange = "gateio" - Bitfinex Exchange = "bitfinex" - Bybit Exchange = "bybit" - Okx Exchange = "okx" -) - -var exchangeGetters = map[Exchange]TickerGetter{ - Coinbase: CoinbaseGetter, - Binance: BinanceGetter, - Kraken: KrakenGetter, - Bitstamp: BitstampGetter, - Huobi: HuobiGetter, - Kucoin: KUCoinGetter, - Gateio: GateIOGetter, - Bitfinex: BitfinexGetter, - Bybit: BybitGetter, - Okx: OkxGetter, -} - func getExchangeTickerPrice(et ExTick) (*ExTickPri, error) { start := time.Now() getter := exchangeGetters[et.Exchange] - price, err := getter(et.Ticker) + ab, err := getter(et.Ticker) if err != nil { return nil, err } elapsed := time.Since(start) if debug { - log.Printf("[%15s]\t[%5s]\t%.2f\n", et, elapsed, price) + log.Printf("[%15s]\t[%5s]\t%s\n", et, elapsed, ab) } - return &ExTickPri{et, price}, nil + return &ExTickPri{et, TickPrice{ab.Ask, ab.Bid}}, nil } func getExchangeTickerPriceAsync(et ExTick, channel chan *ExTickPri) { @@ -183,18 +204,6 @@ func getExchangeTickerPriceAsync(et ExTick, channel chan *ExTickPri) { channel <- etp } -var exchangeSymbols = map[Exchange][]string{ - Coinbase: symbols.Coinbase, - Binance: symbols.Binance, - Kraken: symbols.Kraken, - Bitstamp: symbols.Bitstamp, - Huobi: symbols.Huobi, - Kucoin: symbols.Kucoin, - Gateio: symbols.Gateio, - Bitfinex: symbols.Bitfinex, - Bybit: symbols.Bybit, - Okx: symbols.Okx, -} func findExTick(symbol string) (*ExTick, error) { if hasExchangePrefix(symbol) { @@ -249,13 +258,14 @@ func stringIsInSlice(s string, sl []string) bool { } func searchForExchangeTicker(symbol string) []ExTick { + replacer := strings.NewReplacer("-", "", "/", "", "_", "") found := []ExTick{} for k, v := range exchangeSymbols { for _, t := range v { lowerT := strings.ToLower(t) - lowerTNoDashNoSlash := strings.ReplaceAll(strings.ReplaceAll(lowerT, "-", ""), "/", "") + lowerTNoDashNoSlash := replacer.Replace(lowerT) lowerSymbol := strings.ToLower(symbol) - lowerSymbolNoDashNoSlash := strings.ReplaceAll(strings.ReplaceAll(lowerSymbol, "-", ""), "/", "") + lowerSymbolNoDashNoSlash := replacer.Replace(lowerSymbol) if strings.Contains(lowerTNoDashNoSlash, lowerSymbolNoDashNoSlash) { found = append(found, ExTick{k, t}) } diff --git a/symbols/coinmate.go b/symbols/coinmate.go new file mode 100644 index 0000000..11e0438 --- /dev/null +++ b/symbols/coinmate.go @@ -0,0 +1,22 @@ +package symbols + +var Coinmate = []string{ + "BTC_EUR", + "BTC_CZK", + "USDT_CZK", + "USDT_EUR", + "BTC_USDT", + "ETH_EUR", + "ETH_CZK", + "ETH_BTC", + "ADA_EUR", + "ADA_CZK", + "SOL_EUR", + "SOL_CZK", + "LTC_BTC", + "LTC_EUR", + "LTC_CZK", + "XRP_EUR", + "XRP_CZK", + "XRP_BTC", +}