Skip to content

Commit d4a45a5

Browse files
committed
Support BigOne
1 parent e5d4ba0 commit d4a45a5

File tree

6 files changed

+209
-7
lines changed

6 files changed

+209
-7
lines changed

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
1111
![token-ticker](https://user-images.githubusercontent.com/2657334/40175207-ff9e6504-5a09-11e8-9a3d-a887ebc4895a.png)
1212

13-
Track token prices in your favorite exchanges from the terminal. Best CLI tool for those who are both **Crypto investors** and **Engineers**.
13+
Token-ticker (or `tt` for short) is a CLI tool for those who are both **Crypto investors** and **Engineers**, allowing you to track token prices and changes in your favorite exchanges on the terminal.
1414

1515
### Features
1616

1717
* Auto refresh on a specified interval, watch prices in live update mode
1818
* Proxy aware HTTP request, for easy access to blocked exchanges
19-
* Real-time prices from 9+ exchanges
19+
* Real-time prices from 10+ exchanges
2020

2121
### Supported Exchanges
2222

@@ -29,16 +29,24 @@ Track token prices in your favorite exchanges from the terminal. Best CLI tool f
2929
* [Gate.io](https://gate.io/)
3030
* [Bittrex](https://bittrex.com/)
3131
* [HitBTC](https://hitbtc.com/)
32+
* [BigONE](https://big.one/)
3233
* _still adding..._
3334

3435
### Installation
3536

36-
If you have [Go](https://golang.org/) (1.9+) installed:
37+
#### Homebrew
38+
39+
```bash
40+
# work in process
41+
```
42+
43+
#### Using [Go](https://golang.org/) (1.9+)
3744
```bash
3845
$ go get -u github.com/polyrabbit/token-ticker
3946
```
4047

41-
Or download executable from the [release page](https://github.com/polyrabbit/token-ticker/releases/latest), and put it into your `PATH`.
48+
#### Manually
49+
Download from [release page](https://github.com/polyrabbit/token-ticker/releases/latest) and extact the tarbal into /usr/bin or your `PATH` directory.
4250

4351
### Usage
4452

exchange/bigone.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package exchange
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"github.com/sirupsen/logrus"
8+
"io"
9+
"math"
10+
"net/http"
11+
"strconv"
12+
"strings"
13+
"time"
14+
)
15+
16+
// https://developer.big.one/
17+
const bigOneBaseApi = "https://api.big.one/"
18+
19+
type bigOneClient struct {
20+
exchangeBaseClient
21+
AccessKey string
22+
SecretKey string
23+
}
24+
25+
type bigOneErrorResponse struct {
26+
Error *struct {
27+
Status int
28+
Code int
29+
Description string
30+
}
31+
}
32+
33+
type bigOneMarketResponse struct {
34+
bigOneErrorResponse
35+
Data struct {
36+
Symbol string
37+
Ticker struct {
38+
Price float64 `json:",string"`
39+
}
40+
//Metrics map[string][]interface{}
41+
Metrics struct {
42+
// timestamp, open, close, high, low, volume
43+
Min1 [][]interface{} `json:"0000001"`
44+
Min5 [][]interface{} `json:"0000005"`
45+
Min15 [][]interface{} `json:"0000015"`
46+
}
47+
}
48+
}
49+
50+
func NewBigOneClient(httpClient *http.Client) *bigOneClient {
51+
return &bigOneClient{exchangeBaseClient: *newExchangeBase(bigOneBaseApi, httpClient)}
52+
}
53+
54+
func (client *bigOneClient) GetName() string {
55+
return "BigONE"
56+
}
57+
58+
func (client *bigOneClient) decodeResponse(body io.ReadCloser, respJSON zbCommonResponseProvider) error {
59+
defer body.Close()
60+
61+
decoder := json.NewDecoder(body)
62+
if err := decoder.Decode(&respJSON); err != nil {
63+
return err
64+
}
65+
66+
// All I need is to get the common part, I don't like this
67+
commonResponse := respJSON.getCommonResponse()
68+
if commonResponse.Error != nil {
69+
return errors.New(*commonResponse.Error)
70+
}
71+
if commonResponse.Message != nil {
72+
return errors.New(*commonResponse.Message)
73+
}
74+
return nil
75+
}
76+
77+
func (client *bigOneClient) SearchKlinePriceNear(klineIntervals [][]interface{}, after time.Time) (float64, error) {
78+
var intervalTime time.Time
79+
for _, interval := range klineIntervals {
80+
if ts, ok := interval[0].(float64); ok {
81+
intervalTime = time.Unix(int64(ts)/1000, 0)
82+
if after.Equal(intervalTime) || after.After(intervalTime) {
83+
// Assume candles are sorted in asc order, so the first less than or equal to is the candle looking for
84+
logrus.Debugf("%s - Kline for %v uses open price at %v", client.GetName(), after.Local(), intervalTime.Local())
85+
if openStr, ok := interval[1].(string); ok {
86+
return strconv.ParseFloat(openStr, 64)
87+
} else {
88+
return 0, fmt.Errorf("cannot convert open price item %v of kline to string", interval[1])
89+
}
90+
}
91+
} else {
92+
return 0, fmt.Errorf("cannot convert first item %v of kline to float64", interval[0])
93+
}
94+
}
95+
return 0, fmt.Errorf("no time found right after %v, the last time in this interval is %v", after.Local(), intervalTime.Local())
96+
}
97+
98+
func (client *bigOneClient) GetSymbolPrice(symbol string) (*SymbolPrice, error) {
99+
// One api to get all
100+
resp, err := client.httpGet("markets/"+strings.ToUpper(symbol), nil)
101+
if err != nil {
102+
return nil, err
103+
}
104+
defer resp.Body.Close()
105+
106+
var respJSON bigOneMarketResponse
107+
108+
decoder := json.NewDecoder(resp.Body)
109+
if err := decoder.Decode(&respJSON); err != nil {
110+
return nil, err
111+
}
112+
113+
if respJSON.Error != nil {
114+
return nil, errors.New(respJSON.Error.Description)
115+
}
116+
117+
var (
118+
now = time.Now()
119+
percentChange1h, percentChange24h = math.MaxFloat64, math.MaxFloat64
120+
)
121+
price1hAgo, err := client.SearchKlinePriceNear(respJSON.Data.Metrics.Min1, now.Add(-1*time.Hour))
122+
if price1hAgo == 0 {
123+
// BigOne has a very low volume, that prices after certain amount are all zero, so enlarge intervals here.
124+
price1hAgo, err = client.SearchKlinePriceNear(respJSON.Data.Metrics.Min5, now.Add(-1*time.Hour))
125+
}
126+
if err != nil {
127+
logrus.Warnf("%s - Failed to get price 1 hour ago, error: %v\n", client.GetName(), err)
128+
} else if price1hAgo != 0 {
129+
percentChange1h = (respJSON.Data.Ticker.Price - price1hAgo) / price1hAgo * 100
130+
}
131+
132+
price24hAgo, err := client.SearchKlinePriceNear(respJSON.Data.Metrics.Min15, now.Add(-24*time.Hour))
133+
if err != nil {
134+
logrus.Warnf("%s - Failed to get price 24 hours ago, error: %v\n", client.GetName(), err)
135+
} else if price24hAgo != 0 {
136+
percentChange24h = (respJSON.Data.Ticker.Price - price24hAgo) / price24hAgo * 100
137+
}
138+
139+
return &SymbolPrice{
140+
Symbol: symbol,
141+
Price: strconv.FormatFloat(respJSON.Data.Ticker.Price, 'f', -1, 64),
142+
UpdateAt: time.Now(),
143+
Source: client.GetName(),
144+
PercentChange1h: percentChange1h,
145+
PercentChange24h: percentChange24h,
146+
}, nil
147+
}
148+
149+
func init() {
150+
register((&bigOneClient{}).GetName(), func(client *http.Client) ExchangeClient {
151+
// Limited by type system in Go, I hate wrapper/adapter
152+
return NewBigOneClient(client)
153+
})
154+
}

exchange/bigone_test.go

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

exchange/binance.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func (client *binanceClient) GetSymbolPrice(symbol string) (*SymbolPrice, error)
140140
}
141141

142142
return &SymbolPrice{
143-
Symbol: stat24h.Symbol,
143+
Symbol: symbol,
144144
Price: stat24h.LastPrice,
145145
UpdateAt: time.Unix(stat24h.CloseTime/1000, 0),
146146
Source: client.GetName(),

exchange/coinmarketcap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func (client *coinMarketCapClient) GetSymbolPrice(symbol string) (*SymbolPrice,
7676
token := tokens[0]
7777

7878
return &SymbolPrice{
79-
Symbol: symbol,
79+
Symbol: token.Symbol,
8080
Price: token.PriceUSD,
8181
Source: client.GetName(),
8282
UpdateAt: time.Unix(token.LastUpdated, 0),

token_ticker.example.yaml

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

5858
- name: HitBTC
5959
tokens:
60-
- BTCUSD
60+
- BTCUSD
61+
62+
- name: BigONE
63+
tokens:
64+
- BIG-BTC

0 commit comments

Comments
 (0)