cointools 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -3
- data/README.md +55 -19
- data/bin/{coinmcap → cmcap} +8 -5
- data/bin/coincap +81 -0
- data/bin/cryptowatch +11 -6
- data/lib/cointools/coincap.rb +109 -0
- data/lib/cointools/coinmarketcap.rb +4 -4
- data/lib/cointools/cryptowatch.rb +63 -8
- data/lib/cointools/version.rb +1 -1
- data/lib/cointools.rb +1 -1
- metadata +7 -7
- data/bin/bitbay-price +0 -49
- data/lib/cointools/bitbay.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e581465b03a85e017f097f09f991294a9d87676
|
4
|
+
data.tar.gz: f9e6d0cfeab51850d8d858a50990838305b5a13c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f3237529fa8ad91074c3564ca1268bdbf52d2904e016e2744b4d714f829d3e37feb48614eb5fad7aa2833727f9168c5aa1f731ede1a71330b810f336215612d
|
7
|
+
data.tar.gz: 81833105049ee6fcc85a3ecd323fce79087fd75d25300486922e60031e102f997df4153db3696bbdbdbbe5fb9c64c8d23ac17a5efb0a3b613376a5116e13f5fa
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
|
+
#### Version 0.4.0 (23.05.2018)
|
2
|
+
|
3
|
+
* added CoinCap.io class and `coincap` command
|
4
|
+
* renamed CoinMarketCap command to `cmcap`
|
5
|
+
* removed BitBay class and command - BitBay is now supported on Cryptowatch (including past prices since March)
|
6
|
+
* fixed CoinMarketCap calls with currency conversion
|
7
|
+
* added "fast" method for Cryptowat.ch
|
8
|
+
* include API time allowance in Cryptowat.ch responses
|
9
|
+
|
1
10
|
#### Version 0.3.0 (26.03.2018)
|
2
11
|
|
3
|
-
* added BitBay class and bitbay-price command
|
12
|
+
* added BitBay class and `bitbay-price` command
|
4
13
|
|
5
14
|
#### Version 0.2.1 (6.03.2018)
|
6
15
|
|
@@ -8,11 +17,11 @@
|
|
8
17
|
|
9
18
|
#### Version 0.2.0 (29.01.2018)
|
10
19
|
|
11
|
-
* added CoinMarketCap class & coinmcap binary
|
20
|
+
* added CoinMarketCap class & `coinmcap` binary
|
12
21
|
|
13
22
|
#### Version 0.1.2 (24.01.2018)
|
14
23
|
|
15
|
-
* fixed
|
24
|
+
* fixed `--help` command
|
16
25
|
|
17
26
|
#### Version 0.1.1 (24.01.2018)
|
18
27
|
|
data/README.md
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
-
#
|
1
|
+
# CoinTools
|
2
2
|
|
3
3
|
This is a collection of Ruby scripts and library classes that let you check cryptocurrency prices on various services.
|
4
4
|
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/cointools.svg)](https://badge.fury.io/rb/cointools) [![Build Status](https://travis-ci.org/mackuba/cointools.svg?branch=master)](https://travis-ci.org/mackuba/cointools)
|
6
|
+
|
7
|
+
|
5
8
|
## Installation
|
6
9
|
|
7
|
-
|
10
|
+
CoinTools requires Ruby version 2.3 or newer.
|
11
|
+
|
12
|
+
To use the scripts from the command line, install the gem (depending on your configuration, you might need to add `sudo`):
|
8
13
|
|
9
14
|
```
|
10
15
|
gem install cointools
|
@@ -16,7 +21,9 @@ To use the code as a library, add it to your Gemfile:
|
|
16
21
|
gem 'cointools'
|
17
22
|
```
|
18
23
|
|
19
|
-
##
|
24
|
+
## Cryptowatch
|
25
|
+
|
26
|
+
[Cryptowat.ch](https://cryptowat.ch) is a coin charts site owned by Kraken which tracks historical cryptocurrency prices directly on many exchanges. It provides data from specific markets on specific exchanges, but no general rankings or average market prices.
|
20
27
|
|
21
28
|
To check past price of a given coin on a chosen exchange, pass the exchange and market name and a properly formatted timestamp:
|
22
29
|
|
@@ -57,19 +64,25 @@ puts "#{market} today: #{result.price}"
|
|
57
64
|
|
58
65
|
The result object contains the requested price and (for historical prices) the actual timestamp of the found price, which might slightly differ from the timestamp passed in the argument (the earlier the date, the less precise the result).
|
59
66
|
|
67
|
+
The API is rate limited to 8 seconds of CPU time per hour - you can check the `api_time_spent` and `api_time_remaining` properties of the result object to see how much time allowance you have left.
|
68
|
+
|
69
|
+
If you need to do a large amount of lookups in a short period of time, try the alternative `#get_price_fast` method (or `-f` option on the command line). This method tries to guess which data set is the most appropriate for a given point in the past and requests only that set instead of all of them, which should use significantly less API time allowance. This however relies on an undocumented API behavior, so it's not guaranteed to return data and keep working in the future.
|
70
|
+
|
60
71
|
|
61
|
-
##
|
72
|
+
## CoinMarketCap
|
62
73
|
|
63
|
-
CoinMarketCap
|
74
|
+
[CoinMarketCap](https://coinmarketcap.com) is by far the most popular site for checking coin rankings and BTC/USD prices for all coins available on the market. The API however only returns current average coin prices.
|
75
|
+
|
76
|
+
To look up a coin's price, you need to pass its name as used on CoinMarketCap:
|
64
77
|
|
65
78
|
```
|
66
|
-
|
79
|
+
cmcap power-ledger
|
67
80
|
```
|
68
81
|
|
69
82
|
Alternatively, you can pass the cryptocurrency's symbol using the `-s` or `--symbol` parameter:
|
70
83
|
|
71
84
|
```
|
72
|
-
|
85
|
+
cmcap -s powr
|
73
86
|
```
|
74
87
|
|
75
88
|
However, this operation needs to download a complete ticker for all currencies and scan through the list, so it's recommended to use the name as in the example above.
|
@@ -77,16 +90,16 @@ However, this operation needs to download a complete ticker for all currencies a
|
|
77
90
|
You can also use the `-b` or `--btc-price` flag to request a price in BTC instead of USD:
|
78
91
|
|
79
92
|
```
|
80
|
-
|
93
|
+
cmcap power-ledger -b
|
81
94
|
```
|
82
95
|
|
83
96
|
Or you can request the price in one of the ~30 other supported fiat currencies with `-f` or `--fiat-currency`:
|
84
97
|
|
85
98
|
```
|
86
|
-
|
99
|
+
cmcap request-network -fEUR
|
87
100
|
```
|
88
101
|
|
89
|
-
You can print a list of supported fiat currencies with `
|
102
|
+
You can print a list of supported fiat currencies with `cmcap --list-fiat-currencies`.
|
90
103
|
|
91
104
|
Same things in code:
|
92
105
|
|
@@ -106,28 +119,51 @@ eth = cryptowatch.get_price('ethereum', convert_to: 'EUR')
|
|
106
119
|
puts "ETH: #{eth.converted_price} EUR"
|
107
120
|
```
|
108
121
|
|
122
|
+
The soft rate limit for the API is 10 requests per minute (for the currently used v1 API).
|
123
|
+
|
124
|
+
|
125
|
+
## CoinCap
|
126
|
+
|
127
|
+
[CoinCap.io](https://coincap.io) is a site similar to CoinMarketCap with an API that includes historical coin prices (with decreasing precision the further into the past you look).
|
128
|
+
|
129
|
+
To check past price of a given coin, pass the coin's symbol and a properly formatted timestamp:
|
130
|
+
|
131
|
+
```
|
132
|
+
coincap xmr "2018-04-01 13:00"
|
133
|
+
```
|
134
|
+
|
135
|
+
To check the current price, skip the timestamp:
|
109
136
|
|
110
|
-
|
137
|
+
```
|
138
|
+
coincap xmr
|
139
|
+
```
|
111
140
|
|
112
|
-
|
141
|
+
You can also use the `-b` or `--btc-price` flag to request a price in BTC instead of USD, or `-e` or `--eur-price` for EUR:
|
113
142
|
|
114
143
|
```
|
115
|
-
|
144
|
+
coincap xmr -b
|
145
|
+
coincap xmr -e
|
116
146
|
```
|
117
147
|
|
148
|
+
These are however only supported for current prices - past prices are only listed in USD.
|
149
|
+
|
118
150
|
In code:
|
119
151
|
|
120
152
|
```ruby
|
121
|
-
require 'cointools' # or 'cointools/
|
122
|
-
|
153
|
+
require 'cointools' # or 'cointools/coincap'
|
154
|
+
coincap = CoinTools::CoinCap.new
|
123
155
|
|
124
|
-
|
125
|
-
puts "
|
156
|
+
result = coincap.get_price('XMR', Time.now - 86400)
|
157
|
+
puts "XMR yesterday: $#{result.usd_price} (#{result.time})"
|
126
158
|
|
127
|
-
|
128
|
-
puts "
|
159
|
+
result = coincap.get_current_price('XMR')
|
160
|
+
puts "XMR today: $#{result.usd_price} / €#{result.eur_price} / ₿#{result.btc_price}"
|
129
161
|
```
|
130
162
|
|
163
|
+
The result object contains the requested price and (for historical prices) the actual timestamp of the found price, which might slightly differ from the timestamp passed in the argument (the earlier the date, the less precise the result).
|
164
|
+
|
165
|
+
At the moment there don't seem to be any official rate limits for the API.
|
166
|
+
|
131
167
|
|
132
168
|
## Credits & contributing
|
133
169
|
|
data/bin/{coinmcap → cmcap}
RENAMED
@@ -57,7 +57,7 @@ end
|
|
57
57
|
|
58
58
|
if fiat_currency && btc_price
|
59
59
|
puts "#{$PROGRAM_NAME}: --btc-price and --fiat-currency options cannot be used together"
|
60
|
-
exit
|
60
|
+
exit 1
|
61
61
|
end
|
62
62
|
|
63
63
|
begin
|
@@ -84,13 +84,16 @@ begin
|
|
84
84
|
else
|
85
85
|
puts price
|
86
86
|
end
|
87
|
-
rescue CoinTools::CoinMarketCap::
|
87
|
+
rescue CoinTools::CoinMarketCap::BadRequestException => e
|
88
88
|
$stderr.puts "Error: Incorrect coin name: #{coin_name} (#{e})"
|
89
|
-
exit
|
89
|
+
exit 1
|
90
|
+
rescue CoinTools::CoinMarketCap::InvalidResponseException => e
|
91
|
+
$stderr.puts "Error: Something went wrong: #{e}"
|
92
|
+
exit 1
|
90
93
|
rescue CoinTools::CoinMarketCap::NoDataException => e
|
91
94
|
$stderr.puts "Error: #{e}"
|
92
|
-
exit
|
95
|
+
exit 1
|
93
96
|
rescue CoinTools::CoinMarketCap::InvalidFiatCurrencyException => e
|
94
97
|
$stderr.puts "Error: Unsupported fiat currency: #{fiat_currency}"
|
95
|
-
exit
|
98
|
+
exit 1
|
96
99
|
end
|
data/bin/coincap
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'cointools'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
def print_help
|
10
|
+
puts "Usage: #{$PROGRAM_NAME} <symbol> [<date>] [-b/--btc-price | -e/--eur-price] [-v/--verbose]"
|
11
|
+
puts " e.g.: #{$PROGRAM_NAME} NANO \"2017-12-20 15:00\""
|
12
|
+
puts
|
13
|
+
puts "* -b / --btc-price: returns the coin's price in BTC instead of USD (current prices only)"
|
14
|
+
puts "* -e / --eur-price: returns the coin's price in EUR (current prices only)"
|
15
|
+
end
|
16
|
+
|
17
|
+
verbose = false
|
18
|
+
btc_price = false
|
19
|
+
eur_price = false
|
20
|
+
|
21
|
+
OptionParser.new do |opts|
|
22
|
+
opts.on('-v', '--verbose') { verbose = true }
|
23
|
+
opts.on('-b', '--btc-price') { btc_price = true }
|
24
|
+
opts.on('-e', '--eur-price') { eur_price = true }
|
25
|
+
|
26
|
+
opts.on('-h', '--help') do
|
27
|
+
print_help
|
28
|
+
exit 0
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.parse!
|
32
|
+
end
|
33
|
+
|
34
|
+
if ARGV.length < 1 || ARGV.length > 2
|
35
|
+
print_help
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
symbol = ARGV[0]
|
40
|
+
date = Time.parse(ARGV[1]) if ARGV[1]
|
41
|
+
|
42
|
+
if eur_price && btc_price
|
43
|
+
puts "#{$PROGRAM_NAME}: --btc-price and --eur-price options cannot be used together"
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
|
47
|
+
if date && (eur_price || btc_price)
|
48
|
+
puts "#{$PROGRAM_NAME}: --btc-price and --eur-price options cannot be used for historical prices"
|
49
|
+
exit 1
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
result = CoinTools::CoinCap.new.get_price(symbol, date)
|
54
|
+
|
55
|
+
if btc_price
|
56
|
+
price = result.btc_price
|
57
|
+
unit = 'BTC'
|
58
|
+
elsif eur_price
|
59
|
+
price = result.eur_price
|
60
|
+
unit = 'EUR'
|
61
|
+
else
|
62
|
+
price = result.usd_price
|
63
|
+
unit = 'USD'
|
64
|
+
end
|
65
|
+
|
66
|
+
if verbose
|
67
|
+
puts "#{symbol.upcase} @ #{result.time || Time.now} ==> #{price} #{unit}"
|
68
|
+
puts
|
69
|
+
else
|
70
|
+
puts price
|
71
|
+
end
|
72
|
+
rescue CoinTools::CoinCap::BadRequestException => e
|
73
|
+
$stderr.puts "Error: Incorrect coin name: #{coin_name} (#{e})"
|
74
|
+
exit 1
|
75
|
+
rescue CoinTools::CoinCap::InvalidResponseException => e
|
76
|
+
$stderr.puts "Error: Something went wrong: #{e}"
|
77
|
+
exit 1
|
78
|
+
rescue CoinTools::CoinCap::NoDataException => e
|
79
|
+
$stderr.puts "Error: #{e}"
|
80
|
+
exit 1
|
81
|
+
end
|
data/bin/cryptowatch
CHANGED
@@ -7,9 +7,10 @@ require 'optparse'
|
|
7
7
|
require 'time'
|
8
8
|
|
9
9
|
def print_help
|
10
|
-
puts "Usage: #{$PROGRAM_NAME} <exchange> <market> [<date>] [-v/--verbose]"
|
10
|
+
puts "Usage: #{$PROGRAM_NAME} <exchange> <market> [<date>] [-v/--verbose] [-f/--fast]"
|
11
11
|
puts " e.g.: #{$PROGRAM_NAME} gdax btcusd \"2017-06-30 15:27\""
|
12
12
|
puts
|
13
|
+
puts "* --fast: tries to use less API time allowance at the cost of worse reliability"
|
13
14
|
puts "* To print a list of available exchanges:"
|
14
15
|
puts " #{$PROGRAM_NAME} --list-exchanges"
|
15
16
|
puts "* To print a list of available markets on an exchange:"
|
@@ -17,6 +18,7 @@ def print_help
|
|
17
18
|
end
|
18
19
|
|
19
20
|
verbose = false
|
21
|
+
fast = false
|
20
22
|
|
21
23
|
OptionParser.new do |opts|
|
22
24
|
opts.on('-v', '--verbose') { verbose = true }
|
@@ -26,6 +28,8 @@ OptionParser.new do |opts|
|
|
26
28
|
exit 0
|
27
29
|
end
|
28
30
|
|
31
|
+
opts.on('-f', '--fast') { fast = true }
|
32
|
+
|
29
33
|
opts.on('--list-exchanges') do
|
30
34
|
puts CoinTools::Cryptowatch.new.exchanges
|
31
35
|
exit 0
|
@@ -37,7 +41,7 @@ OptionParser.new do |opts|
|
|
37
41
|
exit 0
|
38
42
|
rescue CoinTools::Cryptowatch::BadRequestException => e
|
39
43
|
$stderr.puts "Error: Incorrect exchange name: #{e}"
|
40
|
-
exit
|
44
|
+
exit 1
|
41
45
|
end
|
42
46
|
end
|
43
47
|
|
@@ -54,7 +58,8 @@ market = ARGV[1].downcase
|
|
54
58
|
date = Time.parse(ARGV[2]) if ARGV[2]
|
55
59
|
|
56
60
|
begin
|
57
|
-
|
61
|
+
method = fast ? :get_price_fast : :get_price
|
62
|
+
result = CoinTools::Cryptowatch.new.send(method, exchange, market, date)
|
58
63
|
|
59
64
|
if verbose
|
60
65
|
puts "#{exchange}:#{market} @ #{result.time || Time.now} ==> #{result.price}"
|
@@ -64,11 +69,11 @@ begin
|
|
64
69
|
end
|
65
70
|
rescue CoinTools::Cryptowatch::InvalidDateException => e
|
66
71
|
$stderr.puts "Error: #{e}"
|
67
|
-
exit
|
72
|
+
exit 1
|
68
73
|
rescue CoinTools::Cryptowatch::BadRequestException => e
|
69
74
|
$stderr.puts "Error: Incorrect exchange or market name (#{e})"
|
70
|
-
exit
|
75
|
+
exit 1
|
71
76
|
rescue CoinTools::Cryptowatch::NoDataException => e
|
72
77
|
$stderr.puts "Error: #{e}: data not ready yet"
|
73
|
-
exit
|
78
|
+
exit 1
|
74
79
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require_relative 'version'
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module CoinTools
|
8
|
+
class CoinCap
|
9
|
+
BASE_URL = "https://coincap.io"
|
10
|
+
USER_AGENT = "cointools/#{CoinTools::VERSION}"
|
11
|
+
|
12
|
+
DataPoint = Struct.new(:time, :usd_price, :eur_price, :btc_price)
|
13
|
+
|
14
|
+
PERIODS = [1, 7, 30, 90, 180, 365]
|
15
|
+
|
16
|
+
class InvalidResponseException < StandardError
|
17
|
+
attr_reader :response
|
18
|
+
|
19
|
+
def initialize(response)
|
20
|
+
super("#{response.code} #{response.message}")
|
21
|
+
@response = response
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class BadRequestException < InvalidResponseException
|
26
|
+
end
|
27
|
+
|
28
|
+
class InvalidDateException < StandardError
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_price(symbol, time = nil)
|
32
|
+
return get_current_price(symbol) if time.nil?
|
33
|
+
|
34
|
+
(time <= Time.now) or raise InvalidDateException.new('Future date was passed')
|
35
|
+
(time.year >= 2009) or raise InvalidDateException.new('Too early date was passed')
|
36
|
+
|
37
|
+
period = period_for_time(time)
|
38
|
+
|
39
|
+
if period
|
40
|
+
url = URI("#{BASE_URL}/history/#{period}day/#{symbol.upcase}")
|
41
|
+
else
|
42
|
+
url = URI("#{BASE_URL}/history/#{symbol.upcase}")
|
43
|
+
end
|
44
|
+
|
45
|
+
unixtime = time.to_i
|
46
|
+
|
47
|
+
response = make_request(url)
|
48
|
+
|
49
|
+
case response
|
50
|
+
when Net::HTTPSuccess
|
51
|
+
json = JSON.load(response.body)
|
52
|
+
data = json['price']
|
53
|
+
|
54
|
+
timestamp, price = best_matching_record(data, unixtime)
|
55
|
+
actual_time = Time.at(timestamp / 1000)
|
56
|
+
|
57
|
+
return DataPoint.new(actual_time, price, nil, nil)
|
58
|
+
when Net::HTTPBadRequest
|
59
|
+
raise BadRequestException.new(response)
|
60
|
+
else
|
61
|
+
raise InvalidResponseException.new(response)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_current_price(symbol)
|
66
|
+
url = URI("#{BASE_URL}/page/#{symbol.upcase}")
|
67
|
+
|
68
|
+
response = make_request(url)
|
69
|
+
|
70
|
+
case response
|
71
|
+
when Net::HTTPSuccess
|
72
|
+
json = JSON.load(response.body)
|
73
|
+
|
74
|
+
usd_price = json['price_usd']
|
75
|
+
eur_price = json['price_eur']
|
76
|
+
btc_price = json['price_btc']
|
77
|
+
|
78
|
+
return DataPoint.new(nil, usd_price, eur_price, btc_price)
|
79
|
+
when Net::HTTPBadRequest
|
80
|
+
raise BadRequestException.new(response)
|
81
|
+
else
|
82
|
+
raise InvalidResponseException.new(response)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def make_request(url)
|
90
|
+
Net::HTTP.start(url.host, url.port, use_ssl: true) do |http|
|
91
|
+
request = Net::HTTP::Get.new(url)
|
92
|
+
request['User-Agent'] = USER_AGENT
|
93
|
+
|
94
|
+
http.request(request)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def best_matching_record(data, unixtime)
|
99
|
+
millitime = unixtime * 1000
|
100
|
+
data.sort_by { |record| (record[0] - millitime).abs }.first
|
101
|
+
end
|
102
|
+
|
103
|
+
def period_for_time(time)
|
104
|
+
now = Time.now
|
105
|
+
|
106
|
+
PERIODS.map { |p| [p, now - p * 86400 + 7200] }.detect { |p, t| t < time }&.first
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -73,7 +73,7 @@ module CoinTools
|
|
73
73
|
end
|
74
74
|
|
75
75
|
return DataPoint.new(timestamp, usd_price, btc_price, converted_price)
|
76
|
-
when Net::
|
76
|
+
when Net::HTTPNotFound
|
77
77
|
raise BadRequestException.new(response)
|
78
78
|
else
|
79
79
|
raise InvalidResponseException.new(response)
|
@@ -86,7 +86,7 @@ module CoinTools
|
|
86
86
|
|
87
87
|
if convert_to
|
88
88
|
validate_fiat_currency(convert_to)
|
89
|
-
url += "&convert=#{convert_to}"
|
89
|
+
url.query += "&convert=#{convert_to}"
|
90
90
|
end
|
91
91
|
|
92
92
|
response = make_request(url)
|
@@ -107,10 +107,10 @@ module CoinTools
|
|
107
107
|
end
|
108
108
|
|
109
109
|
return DataPoint.new(timestamp, usd_price, btc_price, converted_price)
|
110
|
-
when Net::
|
110
|
+
when Net::HTTPNotFound
|
111
111
|
raise BadRequestException.new(response)
|
112
112
|
else
|
113
|
-
raise
|
113
|
+
raise InvalidResponseException.new(response)
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
@@ -9,7 +9,13 @@ module CoinTools
|
|
9
9
|
BASE_URL = "https://api.cryptowat.ch"
|
10
10
|
USER_AGENT = "cointools/#{CoinTools::VERSION}"
|
11
11
|
|
12
|
-
DataPoint = Struct.new(:price, :time)
|
12
|
+
DataPoint = Struct.new(:price, :time, :api_time_spent, :api_time_remaining)
|
13
|
+
|
14
|
+
# we expect this many days worth of data for a given period precision (in seconds); NOT guaranteed by the API
|
15
|
+
DAYS_FOR_PERIODS = {
|
16
|
+
60 => 3, 180 => 10, 300 => 15, 900 => 2 * 30, 1800 => 4 * 30, 3600 => 8 * 30,
|
17
|
+
7200 => 365, 14400 => 1.5 * 365, 21600 => 2 * 365, 43200 => 3 * 365, 86400 => 4 * 365
|
18
|
+
}
|
13
19
|
|
14
20
|
class InvalidResponseException < StandardError
|
15
21
|
attr_reader :response
|
@@ -65,16 +71,17 @@ module CoinTools
|
|
65
71
|
when Net::HTTPSuccess
|
66
72
|
json = JSON.load(response.body)
|
67
73
|
data = json['result']
|
74
|
+
allowance = json['allowance']
|
68
75
|
|
69
76
|
timestamp, o, h, l, c, volume = best_matching_record(data, unixtime, current_time)
|
70
77
|
raise NoDataException.new('No data found for a given time') if timestamp.nil?
|
71
78
|
|
72
79
|
actual_time = Time.at(timestamp)
|
73
|
-
return DataPoint.new(o, actual_time)
|
80
|
+
return DataPoint.new(o, actual_time, allowance['cost'], allowance['remaining'])
|
74
81
|
when Net::HTTPBadRequest
|
75
82
|
raise BadRequestException.new(response)
|
76
83
|
else
|
77
|
-
raise
|
84
|
+
raise InvalidResponseException.new(response)
|
78
85
|
end
|
79
86
|
end
|
80
87
|
|
@@ -87,12 +94,47 @@ module CoinTools
|
|
87
94
|
when Net::HTTPSuccess
|
88
95
|
json = JSON.load(response.body)
|
89
96
|
price = json['result']['price']
|
97
|
+
allowance = json['allowance']
|
98
|
+
|
99
|
+
return DataPoint.new(price, nil, allowance['cost'], allowance['remaining'])
|
100
|
+
when Net::HTTPBadRequest
|
101
|
+
raise BadRequestException.new(response)
|
102
|
+
else
|
103
|
+
raise InvalidResponseException.new(response)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_price_fast(exchange, market, time)
|
108
|
+
(time <= Time.now) or raise InvalidDateException.new('Future date was passed')
|
109
|
+
(time.year >= 2009) or raise InvalidDateException.new('Too early date was passed')
|
110
|
+
|
111
|
+
period = precision_for_time(time)
|
112
|
+
|
113
|
+
if period.nil?
|
114
|
+
return get_price(exchange, market, time)
|
115
|
+
end
|
116
|
+
|
117
|
+
unixtime = time.to_i
|
118
|
+
current_time = Time.now.to_i
|
119
|
+
url = URI("#{BASE_URL}/markets/#{exchange}/#{market}/ohlc?after=#{unixtime}&periods=#{period}")
|
120
|
+
|
121
|
+
response = make_request(url)
|
90
122
|
|
91
|
-
|
123
|
+
case response
|
124
|
+
when Net::HTTPSuccess
|
125
|
+
json = JSON.load(response.body)
|
126
|
+
data = json['result']
|
127
|
+
allowance = json['allowance']
|
128
|
+
|
129
|
+
timestamp, o, h, l, c, volume = best_matching_record(data, unixtime, current_time)
|
130
|
+
raise NoDataException.new('No data found for a given time') if timestamp.nil?
|
131
|
+
|
132
|
+
actual_time = Time.at(timestamp)
|
133
|
+
return DataPoint.new(o, actual_time, allowance['cost'], allowance['remaining'])
|
92
134
|
when Net::HTTPBadRequest
|
93
135
|
raise BadRequestException.new(response)
|
94
136
|
else
|
95
|
-
raise
|
137
|
+
raise InvalidResponseException.new(response)
|
96
138
|
end
|
97
139
|
end
|
98
140
|
|
@@ -132,6 +174,21 @@ module CoinTools
|
|
132
174
|
candidates.sort_by { |record| (record[0] - unixtime).abs }.first
|
133
175
|
end
|
134
176
|
|
177
|
+
def precision_for_time(time)
|
178
|
+
now = Time.now
|
179
|
+
|
180
|
+
DAYS_FOR_PERIODS.keys.sort.each do |period|
|
181
|
+
days = DAYS_FOR_PERIODS[period]
|
182
|
+
earliest_date = now - days * 86400
|
183
|
+
|
184
|
+
if earliest_date < time
|
185
|
+
return period
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
nil
|
190
|
+
end
|
191
|
+
|
135
192
|
def get_exchanges
|
136
193
|
url = URI("#{BASE_URL}/exchanges")
|
137
194
|
|
@@ -141,10 +198,8 @@ module CoinTools
|
|
141
198
|
when Net::HTTPSuccess
|
142
199
|
json = JSON.load(response.body)
|
143
200
|
return json['result'].select { |e| e['active'] == true }.map { |e| e['symbol'] }.sort
|
144
|
-
when Net::HTTPBadRequest
|
145
|
-
raise BadRequestException.new(response)
|
146
201
|
else
|
147
|
-
raise
|
202
|
+
raise InvalidResponseException.new(response)
|
148
203
|
end
|
149
204
|
end
|
150
205
|
end
|
data/lib/cointools/version.rb
CHANGED
data/lib/cointools.rb
CHANGED
metadata
CHANGED
@@ -1,33 +1,33 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cointools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kuba Suder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
15
15
|
- jakub.suder@gmail.com
|
16
16
|
executables:
|
17
|
-
-
|
17
|
+
- coincap
|
18
18
|
- cryptowatch
|
19
|
-
-
|
19
|
+
- cmcap
|
20
20
|
extensions: []
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
23
|
- CHANGELOG.md
|
24
24
|
- MIT-LICENSE.txt
|
25
25
|
- README.md
|
26
|
-
- bin/
|
27
|
-
- bin/
|
26
|
+
- bin/cmcap
|
27
|
+
- bin/coincap
|
28
28
|
- bin/cryptowatch
|
29
29
|
- lib/cointools.rb
|
30
|
-
- lib/cointools/
|
30
|
+
- lib/cointools/coincap.rb
|
31
31
|
- lib/cointools/coinmarketcap.rb
|
32
32
|
- lib/cointools/cryptowatch.rb
|
33
33
|
- lib/cointools/version.rb
|
data/bin/bitbay-price
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'bundler/setup'
|
4
|
-
require 'cointools'
|
5
|
-
|
6
|
-
require 'optparse'
|
7
|
-
require 'time'
|
8
|
-
|
9
|
-
def print_help
|
10
|
-
puts "Usage: #{$PROGRAM_NAME} <market> [-v/--verbose]"
|
11
|
-
puts " e.g.: #{$PROGRAM_NAME} btcusd"
|
12
|
-
end
|
13
|
-
|
14
|
-
verbose = false
|
15
|
-
|
16
|
-
OptionParser.new do |opts|
|
17
|
-
opts.on('-v', '--verbose') { verbose = true }
|
18
|
-
|
19
|
-
opts.on('-h', '--help') do
|
20
|
-
print_help
|
21
|
-
exit 0
|
22
|
-
end
|
23
|
-
|
24
|
-
opts.parse!
|
25
|
-
end
|
26
|
-
|
27
|
-
if ARGV.length != 1
|
28
|
-
print_help
|
29
|
-
exit 1
|
30
|
-
end
|
31
|
-
|
32
|
-
market = ARGV[0].downcase
|
33
|
-
|
34
|
-
begin
|
35
|
-
result = CoinTools::BitBay.new.get_price(market)
|
36
|
-
|
37
|
-
if verbose
|
38
|
-
puts "#{market.upcase} @ #{Time.now} ==> #{result.price}"
|
39
|
-
puts
|
40
|
-
else
|
41
|
-
puts result.price
|
42
|
-
end
|
43
|
-
rescue CoinTools::BitBay::ErrorResponseException => e
|
44
|
-
$stderr.puts "Error: #{e}"
|
45
|
-
exit 2
|
46
|
-
rescue CoinTools::BitBay::BadRequestException => e
|
47
|
-
$stderr.puts "Error: Incorrect market name (#{e})"
|
48
|
-
exit 3
|
49
|
-
end
|
data/lib/cointools/bitbay.rb
DELETED
@@ -1,63 +0,0 @@
|
|
1
|
-
require_relative 'version'
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'net/http'
|
5
|
-
require 'uri'
|
6
|
-
|
7
|
-
module CoinTools
|
8
|
-
class BitBay
|
9
|
-
BASE_URL = "https://bitbay.net/API/Public"
|
10
|
-
USER_AGENT = "cointools/#{CoinTools::VERSION}"
|
11
|
-
|
12
|
-
DataPoint = Struct.new(:price, :time)
|
13
|
-
|
14
|
-
class InvalidResponseException < StandardError
|
15
|
-
attr_reader :response
|
16
|
-
|
17
|
-
def initialize(response)
|
18
|
-
super("#{response.code} #{response.message}")
|
19
|
-
@response = response
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class BadRequestException < InvalidResponseException
|
24
|
-
end
|
25
|
-
|
26
|
-
class ErrorResponseException < StandardError
|
27
|
-
end
|
28
|
-
|
29
|
-
def get_price(market)
|
30
|
-
url = URI("#{BASE_URL}/#{market}/ticker.json")
|
31
|
-
|
32
|
-
response = make_request(url)
|
33
|
-
|
34
|
-
case response
|
35
|
-
when Net::HTTPSuccess
|
36
|
-
json = JSON.load(response.body)
|
37
|
-
|
38
|
-
if json['code']
|
39
|
-
raise ErrorResponseException.new("#{json['code']} #{json['message']}")
|
40
|
-
end
|
41
|
-
|
42
|
-
price = json['last']
|
43
|
-
|
44
|
-
return DataPoint.new(price, nil)
|
45
|
-
when Net::HTTPBadRequest
|
46
|
-
raise BadRequestException.new(response)
|
47
|
-
else
|
48
|
-
raise InvalidResponseException.new(response)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def make_request(url)
|
55
|
-
Net::HTTP.start(url.host, url.port, use_ssl: true) do |http|
|
56
|
-
request = Net::HTTP::Get.new(url)
|
57
|
-
request['User-Agent'] = USER_AGENT
|
58
|
-
|
59
|
-
http.request(request)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|