cointools 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +67 -0
- data/VSPL-LICENSE.txt +5 -0
- data/bin/cryptowatch +65 -0
- data/lib/cointools/cryptowatch.rb +151 -0
- data/lib/cointools/version.rb +3 -0
- data/lib/cointools.rb +5 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0131dd2bc4e77dd203eacdfb8d13f31693f71394
|
4
|
+
data.tar.gz: dfd75a7b004577d0ec16cda14e8a3e896399b526
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 541daf33af1f794e931e1811bb4d25b1efd8d568f4f790e991142126cee37f9bcfcaf07acb1cfe51f86e230686ed22ee05f4c495152ac9a6f75dc708fde70cbe
|
7
|
+
data.tar.gz: 8f1e98132e55e483d4e4fe6db93315f1b4e6591f65fcbee30b666eb22db534d44c585cd341dfe3699eebacdf9a8bfdffe082c7c24598f306900fe75e82a250a5
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Cointools
|
2
|
+
|
3
|
+
This is a collection of Ruby scripts and library classes that let you check cryptocurrency prices on various services (currently Cryptowatch).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
To use the scripts from the command line, install the gem:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install cointools
|
11
|
+
```
|
12
|
+
|
13
|
+
To use the code as a library, add it to your Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'cointools'
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### [Cryptowatch](https://cryptowat.ch)
|
22
|
+
|
23
|
+
To check past price of a given coin on a chosen exchange, pass the exchange and market name and a properly formatted timestamp:
|
24
|
+
|
25
|
+
```
|
26
|
+
cryptowatch bitfinex btcusd "2017-12-17 13:00"
|
27
|
+
```
|
28
|
+
|
29
|
+
To check the current price, skip the timestamp:
|
30
|
+
|
31
|
+
```
|
32
|
+
cryptowatch bitfinex btcusd
|
33
|
+
```
|
34
|
+
|
35
|
+
You can fetch a list of available exchanges and markets using these commands:
|
36
|
+
|
37
|
+
```
|
38
|
+
cryptowatch --list-exchanges
|
39
|
+
cryptowatch --list-markets bithumb
|
40
|
+
```
|
41
|
+
|
42
|
+
In code:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
require 'cointools'
|
46
|
+
cryptowatch = CoinTools::Cryptowatch.new
|
47
|
+
|
48
|
+
exchange = cryptowatch.exchanges.first
|
49
|
+
|
50
|
+
list = cryptowatch.get_markets(exchange)
|
51
|
+
market = list.select { |x| x =~ /ltc/ && x !~ /btc/ }.first.upcase
|
52
|
+
|
53
|
+
result = cryptowatch.get_price(exchange, market, Time.now - 86400)
|
54
|
+
puts "#{market} yesterday: #{result.price}"
|
55
|
+
|
56
|
+
result = cryptowatch.get_current_price(exchange, market)
|
57
|
+
puts "#{market} today: #{result.price}"
|
58
|
+
```
|
59
|
+
|
60
|
+
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).
|
61
|
+
|
62
|
+
|
63
|
+
## Credits & contributing
|
64
|
+
|
65
|
+
Copyright © 2018 [Kuba Suder](https://mackuba.eu). Licensed under [Very Simple Public License](https://github.com/mackuba/cointools/blob/master/VSPL-LICENSE.txt), my custom license that's basically a simplified version of the MIT license that fits in 3 lines.
|
66
|
+
|
67
|
+
If you'd like to help me extend the scripts with some additional features or add support for new services, [send me a pull request](https://github.com/mackuba/cointools/pulls).
|
data/VSPL-LICENSE.txt
ADDED
data/bin/cryptowatch
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'cointools'
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
verbose = false
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.on('-v', '--verbose') { verbose = true }
|
13
|
+
|
14
|
+
opts.on('--list-exchanges') do
|
15
|
+
puts CoinTools::Cryptowatch.new.exchanges
|
16
|
+
exit 0
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('--list-markets EXCHANGE') do |exchange|
|
20
|
+
begin
|
21
|
+
puts CoinTools::Cryptowatch.new.get_markets(exchange)
|
22
|
+
exit 0
|
23
|
+
rescue CoinTools::Cryptowatch::BadRequestException => e
|
24
|
+
$stderr.puts "Error: Incorrect exchange name: #{e}"
|
25
|
+
exit 3
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.parse!
|
30
|
+
end
|
31
|
+
|
32
|
+
if ARGV.length < 2 || ARGV.length > 3
|
33
|
+
puts "Usage: #{$PROGRAM_NAME} <exchange> <market> [<date>] [-v/--verbose]"
|
34
|
+
puts " e.g.: #{$PROGRAM_NAME} gdax btcusd \"2017-06-30 15:27\""
|
35
|
+
puts
|
36
|
+
puts "* To print a list of available exchanges:"
|
37
|
+
puts " #{$PROGRAM_NAME} --list-exchanges"
|
38
|
+
puts "* To print a list of available markets on an exchange:"
|
39
|
+
puts " #{$PROGRAM_NAME} --list-markets kraken"
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
|
43
|
+
exchange = ARGV[0].downcase
|
44
|
+
market = ARGV[1].downcase
|
45
|
+
date = Time.parse(ARGV[2]) if ARGV[2]
|
46
|
+
|
47
|
+
begin
|
48
|
+
result = CoinTools::Cryptowatch.new.get_price(exchange, market, date)
|
49
|
+
|
50
|
+
if verbose
|
51
|
+
puts "#{exchange}:#{market} @ #{result.time || Time.now} ==> #{result.price}"
|
52
|
+
puts
|
53
|
+
else
|
54
|
+
puts result.price
|
55
|
+
end
|
56
|
+
rescue CoinTools::Cryptowatch::InvalidDateException => e
|
57
|
+
$stderr.puts "Error: #{e}"
|
58
|
+
exit 2
|
59
|
+
rescue CoinTools::Cryptowatch::BadRequestException => e
|
60
|
+
$stderr.puts "Error: Incorrect exchange or market name: #{e}"
|
61
|
+
exit 3
|
62
|
+
rescue CoinTools::Cryptowatch::NoDataException => e
|
63
|
+
$stderr.puts "Error: #{e}: data not ready yet"
|
64
|
+
exit 4
|
65
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require_relative 'version'
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module CoinTools
|
8
|
+
class Cryptowatch
|
9
|
+
BASE_URL = "https://api.cryptowat.ch"
|
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 NoDataException < StandardError
|
27
|
+
end
|
28
|
+
|
29
|
+
class InvalidDateException < StandardError
|
30
|
+
end
|
31
|
+
|
32
|
+
def exchanges
|
33
|
+
@exchanges ||= get_exchanges
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_markets(exchange)
|
37
|
+
url = URI("#{BASE_URL}/markets/#{exchange}")
|
38
|
+
|
39
|
+
response = make_request(url)
|
40
|
+
|
41
|
+
case response
|
42
|
+
when Net::HTTPSuccess
|
43
|
+
json = JSON.load(response.body)
|
44
|
+
return json['result'].select { |m| m['active'] == true }.map { |m| m['pair'] }.sort
|
45
|
+
when Net::HTTPBadRequest
|
46
|
+
raise BadRequestException.new(response)
|
47
|
+
else
|
48
|
+
raise Exception.new(response)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_price(exchange, market, time = nil)
|
53
|
+
return get_current_price(exchange, market) if time.nil?
|
54
|
+
|
55
|
+
(time <= Time.now) or raise InvalidDateException.new('Future date was passed')
|
56
|
+
(time.year >= 2009) or raise InvalidDateException.new('Too early date was passed')
|
57
|
+
|
58
|
+
unixtime = time.to_i
|
59
|
+
current_time = Time.now.to_i
|
60
|
+
url = URI("#{BASE_URL}/markets/#{exchange}/#{market}/ohlc?after=#{unixtime}")
|
61
|
+
|
62
|
+
response = make_request(url)
|
63
|
+
|
64
|
+
case response
|
65
|
+
when Net::HTTPSuccess
|
66
|
+
json = JSON.load(response.body)
|
67
|
+
data = json['result']
|
68
|
+
|
69
|
+
timestamp, o, h, l, c, volume = best_matching_record(data, unixtime, current_time)
|
70
|
+
raise NoDataException.new('No data found for a given time') if timestamp.nil?
|
71
|
+
|
72
|
+
actual_time = Time.at(timestamp)
|
73
|
+
return DataPoint.new(o, actual_time)
|
74
|
+
when Net::HTTPBadRequest
|
75
|
+
raise BadRequestException.new(response)
|
76
|
+
else
|
77
|
+
raise Exception.new(response)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_current_price(exchange, market)
|
82
|
+
url = URI("#{BASE_URL}/markets/#{exchange}/#{market}/price")
|
83
|
+
|
84
|
+
response = make_request(url)
|
85
|
+
|
86
|
+
case response
|
87
|
+
when Net::HTTPSuccess
|
88
|
+
json = JSON.load(response.body)
|
89
|
+
price = json['result']['price']
|
90
|
+
|
91
|
+
return DataPoint.new(price, nil)
|
92
|
+
when Net::HTTPBadRequest
|
93
|
+
raise BadRequestException.new(response)
|
94
|
+
else
|
95
|
+
raise Exception.new(response)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def make_request(url)
|
103
|
+
Net::HTTP.start(url.host, url.port, use_ssl: true) do |http|
|
104
|
+
request = Net::HTTP::Get.new(url)
|
105
|
+
request['User-Agent'] = USER_AGENT
|
106
|
+
|
107
|
+
http.request(request)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def best_matching_record(data, unixtime, request_time)
|
112
|
+
candidates = []
|
113
|
+
|
114
|
+
data.keys.sort_by { |k| k.to_i }.each do |k|
|
115
|
+
records = data[k]
|
116
|
+
previous = nil
|
117
|
+
|
118
|
+
records.each do |record|
|
119
|
+
timestamp, o, h, l, c, volume = record
|
120
|
+
|
121
|
+
if timestamp >= unixtime
|
122
|
+
candidates.push(record) unless timestamp > request_time
|
123
|
+
break
|
124
|
+
else
|
125
|
+
previous = record
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
candidates.push(previous) if previous
|
130
|
+
end
|
131
|
+
|
132
|
+
candidates.sort_by { |record| (record[0] - unixtime).abs }.first
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_exchanges
|
136
|
+
url = URI("#{BASE_URL}/exchanges")
|
137
|
+
|
138
|
+
response = make_request(url)
|
139
|
+
|
140
|
+
case response
|
141
|
+
when Net::HTTPSuccess
|
142
|
+
json = JSON.load(response.body)
|
143
|
+
return json['result'].select { |e| e['active'] == true }.map { |e| e['symbol'] }.sort
|
144
|
+
when Net::HTTPBadRequest
|
145
|
+
raise BadRequestException.new(response)
|
146
|
+
else
|
147
|
+
raise Exception.new(response)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/cointools.rb
ADDED
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cointools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kuba Suder
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-24 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- jakub.suder@gmail.com
|
16
|
+
executables:
|
17
|
+
- cryptowatch
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- VSPL-LICENSE.txt
|
23
|
+
- bin/cryptowatch
|
24
|
+
- lib/cointools.rb
|
25
|
+
- lib/cointools/cryptowatch.rb
|
26
|
+
- lib/cointools/version.rb
|
27
|
+
homepage: https://github.com/mackuba/cointools
|
28
|
+
licenses:
|
29
|
+
- Nonstandard
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.5.1
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: A collection of scripts for checking cryptocurrency prices.
|
51
|
+
test_files: []
|