Skip to content

Commit

Permalink
Support Coinbase
Browse files Browse the repository at this point in the history
  • Loading branch information
polyrabbit committed Jun 12, 2019
1 parent bfe5173 commit aa3c84c
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@

# Prject configuration by Goland
.idea/
.vscode/

vendor/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Token-ticker (or `tt` for short) is a CLI tool for those who are both **Crypto i
* ~~[BigONE](https://big.one/)~~
* [Poloniex](https://poloniex.com/)
* [Kraken](https://www.kraken.com/)
* [Coinbase](https://www.coinbase.com/)
* _still adding..._

### Installation
Expand Down
4 changes: 4 additions & 0 deletions exchange/bittrex.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"math"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -145,6 +146,9 @@ func (client *bittrexClient) GetSymbolPrice(symbol string) (*SymbolPrice, error)
logrus.Warnf("%s - Failed to get kline ticks, error: %v", client.GetName(), err)
} else {
now := time.Now()
sort.Slice(klineResp.Result, func(i, j int) bool {
return klineResp.Result[i].Timestamp < klineResp.Result[j].Timestamp
})

lastHour := now.Add(-1 * time.Hour)
price1hAgo, err := client.GetPriceRightAfter(klineResp, lastHour)
Expand Down
93 changes: 93 additions & 0 deletions exchange/coinbase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package exchange

import (
"fmt"
"github.com/preichenberger/go-coinbasepro/v2"
"github.com/sirupsen/logrus"
"math"
"net/http"
"sort"
"strconv"
"time"
)

type coinbaseClient struct {
coinbasepro *coinbasepro.Client
}

//type coinbaseClient *coinbasepro.Client

func NewCoinBaseClient(httpClient *http.Client) *coinbaseClient {
client := coinbasepro.NewClient()
client.HTTPClient = httpClient
return &coinbaseClient{coinbasepro: client}
}

func (client *coinbaseClient) GetName() string {
return "Coinbase"
}

func (client *coinbaseClient) GetPriceRightAfter(candles []coinbasepro.HistoricRate, after time.Time) (float64, error) {
for _, candle := range candles {
if after.Equal(candle.Time) || after.After(candle.Time) {
// Assume candles are sorted in desc order, so the first less than or equal to is the candle looking for
logrus.Debugf("%s - Kline for %v uses open price at %v", client.GetName(), after.Local(), candle.Time.Local())
return candle.Open, nil
}
}
return 0, fmt.Errorf("no time found right after %v", after)
}

func (client *coinbaseClient) GetSymbolPrice(symbol string) (*SymbolPrice, error) {
ticker, err := client.coinbasepro.GetTicker(symbol)
if err != nil {
return nil, err
}
currentPrice, err := strconv.ParseFloat(ticker.Price, 64)
if err != nil {
return nil, err
}

var percentChange1h, percentChange24h = math.MaxFloat64, math.MaxFloat64
candles, err := client.coinbasepro.GetHistoricRates(symbol, coinbasepro.GetHistoricRatesParams{
Granularity: 300,
})
if err != nil {
logrus.Warnf("%s - Failed to get kline ticks, error: %v", client.GetName(), err)
} else {
now := time.Now()
sort.Slice(candles, func(i, j int) bool { return candles[i].Time.After(candles[j].Time) })

lastHour := now.Add(-1 * time.Hour)
price1hAgo, err := client.GetPriceRightAfter(candles, lastHour)
if err != nil {
logrus.Warnf("%s - Failed to get price 1 hour ago, error: %v\n", client.GetName(), err)
} else if price1hAgo != 0 {
percentChange1h = (currentPrice - price1hAgo) / price1hAgo * 100
}

last24Hour := now.Add(-24 * time.Hour)
price24hAgo, err := client.GetPriceRightAfter(candles, last24Hour)
if err != nil {
logrus.Warnf("%s - Failed to get price 24 hours ago, error: %v\n", client.GetName(), err)
} else if price24hAgo != 0 {
percentChange24h = (currentPrice - price24hAgo) / price24hAgo * 100
}
}

return &SymbolPrice{
Symbol: symbol,
Price: ticker.Price,
UpdateAt: time.Time(ticker.Time),
Source: client.GetName(),
PercentChange1h: percentChange1h,
PercentChange24h: percentChange24h,
}, nil
}

func init() {
register((&coinbaseClient{}).GetName(), func(client *http.Client) ExchangeClient {
// Limited by type system in Go, I hate wrapper/adapter
return NewCoinBaseClient(client)
})
}
37 changes: 37 additions & 0 deletions exchange/coinbase_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package exchange

import (
"math"
"net/http"
"testing"
)

func TestCoinbaseClient(t *testing.T) {

var client = NewCoinBaseClient(http.DefaultClient)

t.Run("GetSymbolPrice", func(t *testing.T) {
sp, err := client.GetSymbolPrice("BTC-USd")

if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if sp.Price == "" {
t.Fatalf("Get an empty price?")
}
if sp.PercentChange1h == math.MaxFloat64 {
t.Logf("WARNING - PercentChange1h unset?")
}
if sp.PercentChange24h == math.MaxFloat64 {
t.Logf("WARNING - PercentChange24h unset?")
}
})

t.Run("GetUnexistSymbolPrice", func(t *testing.T) {
_, err := client.GetSymbolPrice("ABC123")

if err == nil {
t.Fatalf("Should throws on invalid symbol")
}
})
}
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module token-ticker
module github.com/polyrabbit/token-ticker

require (
github.com/BurntSushi/toml v0.3.1 // indirect
Expand All @@ -17,15 +17,16 @@ require (
github.com/onsi/gomega v1.5.0 // indirect
github.com/pelletier/go-toml v0.0.0-20180323185243-66540cf1fcd2 // indirect
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c
github.com/polyrabbit/token-ticker v0.3.0
github.com/preichenberger/go-coinbasepro/v2 v2.0.4
github.com/sirupsen/logrus v0.0.0-20180515044140-bde08903c767
github.com/spf13/afero v0.0.0-20180401205752-63644898a8da // indirect
github.com/spf13/cast v0.0.0-20180214174949-8965335b8c71 // indirect
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect
github.com/spf13/pflag v0.0.0-20180412120913-583c0c0531f0
github.com/spf13/viper v0.0.0-20180319185019-b5e8006cbee9
github.com/stretchr/testify v1.3.0 // indirect
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78 // indirect
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
)
18 changes: 12 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd h1:1e+0Z+T4t1mKL5xxvxXh5FkjuiToQGKreCobLu7lR3Y=
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosuri/uilive v0.0.3 h1:kvo6aB3pez9Wbudij8srWo4iY6SFTTxTKOkb+uRCE8I=
github.com/gosuri/uilive v0.0.3/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
Expand Down Expand Up @@ -42,8 +42,8 @@ github.com/pkg/errors v0.0.0-20180311214515-816c9085562c h1:F5RoIh7F9wB47PvXvpP1
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyrabbit/token-ticker v0.3.0 h1:bVofYIbG053qVMRgjwAoNmPNJHVGdEncqh3BWLC8LJs=
github.com/polyrabbit/token-ticker v0.3.0/go.mod h1:kWlLHh2DyWKzWhKmys0HirUBN5Vuwn+50xTUNgGlFYw=
github.com/preichenberger/go-coinbasepro/v2 v2.0.4 h1:9A9hFh+uz6wuO8yBaSKsyIpvmUBEOP2i540eG4nudPo=
github.com/preichenberger/go-coinbasepro/v2 v2.0.4/go.mod h1:tsiN/OFQ5FiE+T2i3r88GHDVvR/Jxkx+CGKw7JSYLrE=
github.com/sirupsen/logrus v0.0.0-20180515044140-bde08903c767 h1:fJnpbJk26uSSSSfJZMKP3awEIMTVdZFwHVsbrh7nFsM=
github.com/sirupsen/logrus v0.0.0-20180515044140-bde08903c767/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/spf13/afero v0.0.0-20180401205752-63644898a8da h1:xOGCSnwz3fFyVwFsQ5zVdikAZ6JNM8gePBY+nZmMd80=
Expand All @@ -59,14 +59,20 @@ github.com/spf13/viper v0.0.0-20180319185019-b5e8006cbee9/go.mod h1:A8kyI5cUJhb8
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78 h1:uJIReYEB1ZZLarzi83Pmig1HhZ/cwFCysx05l0PFBIk=
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
Expand Down
6 changes: 5 additions & 1 deletion token_ticker.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,8 @@ exchanges:

- name: Kraken
tokens:
- EOSETH
- EOSETH

- name: Coinbase
tokens:
- BTC-USD

0 comments on commit aa3c84c

Please sign in to comment.