Skip to content

Commit aa3c84c

Browse files
committed
Support Coinbase
1 parent bfe5173 commit aa3c84c

File tree

8 files changed

+157
-10
lines changed

8 files changed

+157
-10
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313

1414
# Prject configuration by Goland
1515
.idea/
16+
.vscode/
1617

1718
vendor/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Token-ticker (or `tt` for short) is a CLI tool for those who are both **Crypto i
3232
* ~~[BigONE](https://big.one/)~~
3333
* [Poloniex](https://poloniex.com/)
3434
* [Kraken](https://www.kraken.com/)
35+
* [Coinbase](https://www.coinbase.com/)
3536
* _still adding..._
3637

3738
### Installation

exchange/bittrex.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"math"
99
"net/http"
1010
"net/url"
11+
"sort"
1112
"strconv"
1213
"strings"
1314
"time"
@@ -145,6 +146,9 @@ func (client *bittrexClient) GetSymbolPrice(symbol string) (*SymbolPrice, error)
145146
logrus.Warnf("%s - Failed to get kline ticks, error: %v", client.GetName(), err)
146147
} else {
147148
now := time.Now()
149+
sort.Slice(klineResp.Result, func(i, j int) bool {
150+
return klineResp.Result[i].Timestamp < klineResp.Result[j].Timestamp
151+
})
148152

149153
lastHour := now.Add(-1 * time.Hour)
150154
price1hAgo, err := client.GetPriceRightAfter(klineResp, lastHour)

exchange/coinbase.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package exchange
2+
3+
import (
4+
"fmt"
5+
"github.com/preichenberger/go-coinbasepro/v2"
6+
"github.com/sirupsen/logrus"
7+
"math"
8+
"net/http"
9+
"sort"
10+
"strconv"
11+
"time"
12+
)
13+
14+
type coinbaseClient struct {
15+
coinbasepro *coinbasepro.Client
16+
}
17+
18+
//type coinbaseClient *coinbasepro.Client
19+
20+
func NewCoinBaseClient(httpClient *http.Client) *coinbaseClient {
21+
client := coinbasepro.NewClient()
22+
client.HTTPClient = httpClient
23+
return &coinbaseClient{coinbasepro: client}
24+
}
25+
26+
func (client *coinbaseClient) GetName() string {
27+
return "Coinbase"
28+
}
29+
30+
func (client *coinbaseClient) GetPriceRightAfter(candles []coinbasepro.HistoricRate, after time.Time) (float64, error) {
31+
for _, candle := range candles {
32+
if after.Equal(candle.Time) || after.After(candle.Time) {
33+
// Assume candles are sorted in desc order, so the first less than or equal to is the candle looking for
34+
logrus.Debugf("%s - Kline for %v uses open price at %v", client.GetName(), after.Local(), candle.Time.Local())
35+
return candle.Open, nil
36+
}
37+
}
38+
return 0, fmt.Errorf("no time found right after %v", after)
39+
}
40+
41+
func (client *coinbaseClient) GetSymbolPrice(symbol string) (*SymbolPrice, error) {
42+
ticker, err := client.coinbasepro.GetTicker(symbol)
43+
if err != nil {
44+
return nil, err
45+
}
46+
currentPrice, err := strconv.ParseFloat(ticker.Price, 64)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
var percentChange1h, percentChange24h = math.MaxFloat64, math.MaxFloat64
52+
candles, err := client.coinbasepro.GetHistoricRates(symbol, coinbasepro.GetHistoricRatesParams{
53+
Granularity: 300,
54+
})
55+
if err != nil {
56+
logrus.Warnf("%s - Failed to get kline ticks, error: %v", client.GetName(), err)
57+
} else {
58+
now := time.Now()
59+
sort.Slice(candles, func(i, j int) bool { return candles[i].Time.After(candles[j].Time) })
60+
61+
lastHour := now.Add(-1 * time.Hour)
62+
price1hAgo, err := client.GetPriceRightAfter(candles, lastHour)
63+
if err != nil {
64+
logrus.Warnf("%s - Failed to get price 1 hour ago, error: %v\n", client.GetName(), err)
65+
} else if price1hAgo != 0 {
66+
percentChange1h = (currentPrice - price1hAgo) / price1hAgo * 100
67+
}
68+
69+
last24Hour := now.Add(-24 * time.Hour)
70+
price24hAgo, err := client.GetPriceRightAfter(candles, last24Hour)
71+
if err != nil {
72+
logrus.Warnf("%s - Failed to get price 24 hours ago, error: %v\n", client.GetName(), err)
73+
} else if price24hAgo != 0 {
74+
percentChange24h = (currentPrice - price24hAgo) / price24hAgo * 100
75+
}
76+
}
77+
78+
return &SymbolPrice{
79+
Symbol: symbol,
80+
Price: ticker.Price,
81+
UpdateAt: time.Time(ticker.Time),
82+
Source: client.GetName(),
83+
PercentChange1h: percentChange1h,
84+
PercentChange24h: percentChange24h,
85+
}, nil
86+
}
87+
88+
func init() {
89+
register((&coinbaseClient{}).GetName(), func(client *http.Client) ExchangeClient {
90+
// Limited by type system in Go, I hate wrapper/adapter
91+
return NewCoinBaseClient(client)
92+
})
93+
}

exchange/coinbase_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package exchange
2+
3+
import (
4+
"math"
5+
"net/http"
6+
"testing"
7+
)
8+
9+
func TestCoinbaseClient(t *testing.T) {
10+
11+
var client = NewCoinBaseClient(http.DefaultClient)
12+
13+
t.Run("GetSymbolPrice", func(t *testing.T) {
14+
sp, err := client.GetSymbolPrice("BTC-USd")
15+
16+
if err != nil {
17+
t.Fatalf("Unexpected error: %v", err)
18+
}
19+
if sp.Price == "" {
20+
t.Fatalf("Get an empty price?")
21+
}
22+
if sp.PercentChange1h == math.MaxFloat64 {
23+
t.Logf("WARNING - PercentChange1h unset?")
24+
}
25+
if sp.PercentChange24h == math.MaxFloat64 {
26+
t.Logf("WARNING - PercentChange24h unset?")
27+
}
28+
})
29+
30+
t.Run("GetUnexistSymbolPrice", func(t *testing.T) {
31+
_, err := client.GetSymbolPrice("ABC123")
32+
33+
if err == nil {
34+
t.Fatalf("Should throws on invalid symbol")
35+
}
36+
})
37+
}

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module token-ticker
1+
module github.com/polyrabbit/token-ticker
22

33
require (
44
github.com/BurntSushi/toml v0.3.1 // indirect
@@ -17,15 +17,16 @@ require (
1717
github.com/onsi/gomega v1.5.0 // indirect
1818
github.com/pelletier/go-toml v0.0.0-20180323185243-66540cf1fcd2 // indirect
1919
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c
20-
github.com/polyrabbit/token-ticker v0.3.0
20+
github.com/preichenberger/go-coinbasepro/v2 v2.0.4
2121
github.com/sirupsen/logrus v0.0.0-20180515044140-bde08903c767
2222
github.com/spf13/afero v0.0.0-20180401205752-63644898a8da // indirect
2323
github.com/spf13/cast v0.0.0-20180214174949-8965335b8c71 // indirect
2424
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect
2525
github.com/spf13/pflag v0.0.0-20180412120913-583c0c0531f0
2626
github.com/spf13/viper v0.0.0-20180319185019-b5e8006cbee9
2727
github.com/stretchr/testify v1.3.0 // indirect
28-
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78 // indirect
28+
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
29+
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
2930
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
3031
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
3132
)

go.sum

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
1111
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
1212
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
1313
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
14-
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd h1:1e+0Z+T4t1mKL5xxvxXh5FkjuiToQGKreCobLu7lR3Y=
15-
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
14+
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
15+
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
1616
github.com/gosuri/uilive v0.0.3 h1:kvo6aB3pez9Wbudij8srWo4iY6SFTTxTKOkb+uRCE8I=
1717
github.com/gosuri/uilive v0.0.3/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
1818
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
@@ -42,8 +42,8 @@ github.com/pkg/errors v0.0.0-20180311214515-816c9085562c h1:F5RoIh7F9wB47PvXvpP1
4242
github.com/pkg/errors v0.0.0-20180311214515-816c9085562c/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
4343
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4444
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
45-
github.com/polyrabbit/token-ticker v0.3.0 h1:bVofYIbG053qVMRgjwAoNmPNJHVGdEncqh3BWLC8LJs=
46-
github.com/polyrabbit/token-ticker v0.3.0/go.mod h1:kWlLHh2DyWKzWhKmys0HirUBN5Vuwn+50xTUNgGlFYw=
45+
github.com/preichenberger/go-coinbasepro/v2 v2.0.4 h1:9A9hFh+uz6wuO8yBaSKsyIpvmUBEOP2i540eG4nudPo=
46+
github.com/preichenberger/go-coinbasepro/v2 v2.0.4/go.mod h1:tsiN/OFQ5FiE+T2i3r88GHDVvR/Jxkx+CGKw7JSYLrE=
4747
github.com/sirupsen/logrus v0.0.0-20180515044140-bde08903c767 h1:fJnpbJk26uSSSSfJZMKP3awEIMTVdZFwHVsbrh7nFsM=
4848
github.com/sirupsen/logrus v0.0.0-20180515044140-bde08903c767/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
4949
github.com/spf13/afero v0.0.0-20180401205752-63644898a8da h1:xOGCSnwz3fFyVwFsQ5zVdikAZ6JNM8gePBY+nZmMd80=
@@ -59,14 +59,20 @@ github.com/spf13/viper v0.0.0-20180319185019-b5e8006cbee9/go.mod h1:A8kyI5cUJhb8
5959
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6060
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
6161
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
62-
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78 h1:uJIReYEB1ZZLarzi83Pmig1HhZ/cwFCysx05l0PFBIk=
63-
golang.org/x/crypto v0.0.0-20180515001509-1a580b3eff78/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
62+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
63+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
6464
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
6565
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
66+
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
67+
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
6668
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
6769
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
70+
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
71+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
6872
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
6973
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
74+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
75+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
7076
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
7177
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
7278
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=

token_ticker.example.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,8 @@ exchanges:
7878

7979
- name: Kraken
8080
tokens:
81-
- EOSETH
81+
- EOSETH
82+
83+
- name: Coinbase
84+
tokens:
85+
- BTC-USD

0 commit comments

Comments
 (0)