kaesen 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env.sample +15 -0
- data/.gitignore +44 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kaesen.gemspec +34 -0
- data/lib/kaesen.rb +17 -0
- data/lib/kaesen/bitflyer.rb +390 -0
- data/lib/kaesen/bitflyerfx.rb +26 -0
- data/lib/kaesen/btcbox.rb +294 -0
- data/lib/kaesen/client.rb +65 -0
- data/lib/kaesen/coincheck.rb +371 -0
- data/lib/kaesen/kraken.rb +156 -0
- data/lib/kaesen/lakebtc.rb +154 -0
- data/lib/kaesen/market.rb +188 -0
- data/lib/kaesen/monetago.rb +110 -0
- data/lib/kaesen/quoine.rb +155 -0
- data/lib/kaesen/version.rb +3 -0
- data/lib/kaesen/zaif.rb +382 -0
- metadata +129 -0
@@ -0,0 +1,156 @@
|
|
1
|
+
require_relative 'market.rb'
|
2
|
+
require 'net/http'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'bigdecimal'
|
6
|
+
|
7
|
+
module Kaesen
|
8
|
+
# Kraken Wrapper Class
|
9
|
+
# https://www.kraken.com/help/api
|
10
|
+
|
11
|
+
class Kraken < Market
|
12
|
+
@@nonce = 0
|
13
|
+
|
14
|
+
def initialize()
|
15
|
+
super()
|
16
|
+
@name = "Kraken"
|
17
|
+
@api_key = ENV["KRAKEN_KEY"]
|
18
|
+
@api_secret = ENV["KRAKEN_SECRET"]
|
19
|
+
@url_public = "https://api.kraken.com/0/public"
|
20
|
+
@url_private = "https://api.kraken.com/0/private"
|
21
|
+
end
|
22
|
+
|
23
|
+
#############################################################
|
24
|
+
# API for public information
|
25
|
+
#############################################################
|
26
|
+
|
27
|
+
# Get ticker information.
|
28
|
+
# @return [hash] ticker
|
29
|
+
# ask: [BigDecimal] 最良売気配値
|
30
|
+
# bid: [BigDecimal] 最良買気配値
|
31
|
+
# last: [BigDecimal] 最近値(?用語要チェック), last price
|
32
|
+
# high: [BigDecimal] 高値
|
33
|
+
# low: [BigDecimal] 安値
|
34
|
+
# volume: [BigDecimal] 取引量
|
35
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
36
|
+
# vwap: [BigDecimal] 過去24時間の加重平均
|
37
|
+
def ticker
|
38
|
+
h = get_ssl(@url_public + "/Ticker?pair=XXBTZJPY") # cf. XBTJPY is alias of XXBTZJPY
|
39
|
+
h = h["XXBTZJPY"]
|
40
|
+
{
|
41
|
+
"ask" => BigDecimal.new(h["a"][0]),
|
42
|
+
"bid" => BigDecimal.new(h["b"][0]),
|
43
|
+
"last" => BigDecimal.new(h["c"][0]),
|
44
|
+
"high" => BigDecimal.new(h["h"][1]), # of the previous 24 hours
|
45
|
+
"low" => BigDecimal.new(h["l"][1]), # of the previous 24 hours
|
46
|
+
"volume" => BigDecimal.new(h["v"][1]), # of the previous 24 hours
|
47
|
+
"ltimestamp" => Time.now.to_i,
|
48
|
+
"vwap" => BigDecimal.new(h["p"][1]), # of the previous 24 hours
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get order book.
|
53
|
+
# @abstract
|
54
|
+
# @return [hash] array of market depth
|
55
|
+
# asks: [Array] 売りオーダー
|
56
|
+
# price : [BigDecimal]
|
57
|
+
# size : [BigDecimal]
|
58
|
+
# bids: [Array] 買いオーダー
|
59
|
+
# price : [BigDecimal]
|
60
|
+
# size : [BigDecimal]
|
61
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
62
|
+
def depth
|
63
|
+
h = get_ssl(@url_public + "/Depth?pair=XXBTZJPY")
|
64
|
+
h = h["XXBTZJPY"]
|
65
|
+
{
|
66
|
+
"asks" => h["asks"].map{|a,b,t| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]}, # to_s でないと誤差が生じる
|
67
|
+
"bids" => h["bids"].map{|a,b,t| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]}, # to_s でないと誤差が生じる
|
68
|
+
"ltimestamp" => Time.now.to_i,
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def initialize_https(uri)
|
75
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
76
|
+
https.use_ssl = true
|
77
|
+
https.open_timeout = 5
|
78
|
+
https.read_timeout = 15
|
79
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
80
|
+
https.verify_depth = 5
|
81
|
+
https
|
82
|
+
end
|
83
|
+
|
84
|
+
# Connect to address via https, and return json response.
|
85
|
+
def get_ssl(address)
|
86
|
+
uri = URI.parse(address)
|
87
|
+
|
88
|
+
begin
|
89
|
+
https = initialize_https(uri)
|
90
|
+
https.start {|w|
|
91
|
+
response = w.get(uri.request_uri)
|
92
|
+
case response
|
93
|
+
when Net::HTTPSuccess
|
94
|
+
json = JSON.parse(response.body)
|
95
|
+
raise JSONException, response.body if json == nil
|
96
|
+
raise APIErrorException, json["error"] unless json["error"].empty?
|
97
|
+
return json["result"]
|
98
|
+
else
|
99
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}."
|
100
|
+
end
|
101
|
+
}
|
102
|
+
rescue
|
103
|
+
raise
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_nonce
|
108
|
+
pre_nonce = @@nonce
|
109
|
+
next_nonce = (Time.now.to_i) * 100
|
110
|
+
|
111
|
+
if next_nonce <= pre_nonce
|
112
|
+
@@nonce = pre_nonce + 1
|
113
|
+
else
|
114
|
+
@@nonce = next_nonce
|
115
|
+
end
|
116
|
+
|
117
|
+
return @@nonce
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_sign(req)
|
121
|
+
secret = @api_secret
|
122
|
+
text = req.body
|
123
|
+
|
124
|
+
OpenSSL::HMAC::hexdigest(OpenSSL::Digest.new('sha512'), secret, text)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Connect to address via https, and return json response.
|
128
|
+
def post_ssl(address, data={})
|
129
|
+
uri = URI.parse(address)
|
130
|
+
data["nonce"] = get_nonce
|
131
|
+
|
132
|
+
begin
|
133
|
+
req = Net::HTTP::Post.new(uri)
|
134
|
+
req.set_form_data(data)
|
135
|
+
req["Key"] = @api_key
|
136
|
+
req["Sign"] = get_sign(req)
|
137
|
+
|
138
|
+
https = initialize_https(uri)
|
139
|
+
https.start {|w|
|
140
|
+
response = w.request(req)
|
141
|
+
case response
|
142
|
+
when Net::HTTPSuccess
|
143
|
+
json = JSON.parse(response.body)
|
144
|
+
raise JSONException, response.body if json == nil
|
145
|
+
return json
|
146
|
+
else
|
147
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}: " + response.value
|
148
|
+
end
|
149
|
+
}
|
150
|
+
rescue
|
151
|
+
raise
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require_relative 'market.rb'
|
2
|
+
require 'net/http'
|
3
|
+
require 'openssl'
|
4
|
+
require 'json'
|
5
|
+
require 'bigdecimal'
|
6
|
+
|
7
|
+
module Kaesen
|
8
|
+
# Lakebtc Wrapper Class
|
9
|
+
# https://www.lakebtc.com/s/api
|
10
|
+
|
11
|
+
class Lakebtc < Market
|
12
|
+
@@nonce = 0
|
13
|
+
|
14
|
+
def initialize()
|
15
|
+
super()
|
16
|
+
@name = "Lakebtc"
|
17
|
+
@api_key = ENV["LAKEBTC_KEY"]
|
18
|
+
@api_secret = ENV["LAKEBTC_SECRET"]
|
19
|
+
@url_public = "https://www.LakeBTC.com/api_v1"
|
20
|
+
@url_private = @url_public
|
21
|
+
end
|
22
|
+
|
23
|
+
#############################################################
|
24
|
+
# API for public information
|
25
|
+
#############################################################
|
26
|
+
|
27
|
+
# Get ticker information.
|
28
|
+
# @return [hash] ticker
|
29
|
+
# ask: [BigDecimal] 最良売気配値
|
30
|
+
# bid: [BigDecimal] 最良買気配値
|
31
|
+
# last: [BigDecimal] 最近値(?用語要チェック), last price
|
32
|
+
# high: [BigDecimal] 高値
|
33
|
+
# low: [BigDecimal] 安値
|
34
|
+
# volume: [BigDecimal] 取引量
|
35
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
36
|
+
# vwap: [BigDecimal] 過去24時間の加重平均
|
37
|
+
def ticker
|
38
|
+
h = get_ssl(@url_public + "/ticker") # the id of BTCJPY is 5.
|
39
|
+
h = h["JPY"]
|
40
|
+
{
|
41
|
+
"ask" => BigDecimal.new(h["ask"].to_s),
|
42
|
+
"bid" => BigDecimal.new(h["bid"].to_s),
|
43
|
+
"last" => BigDecimal.new(h["last"].to_s),
|
44
|
+
# "high"
|
45
|
+
# "low"
|
46
|
+
# "volume"
|
47
|
+
"ltimestamp" => Time.now.to_i,
|
48
|
+
# "vwap"
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get order book.
|
53
|
+
# @abstract
|
54
|
+
# @return [hash] array of market depth
|
55
|
+
# asks: [Array] 売りオーダー
|
56
|
+
# price : [BigDecimal]
|
57
|
+
# size : [BigDecimal]
|
58
|
+
# bids: [Array] 買いオーダー
|
59
|
+
# price : [BigDecimal]
|
60
|
+
# size : [BigDecimal]
|
61
|
+
# ltimestamp: [int] ローカルタイムスタンプ
|
62
|
+
def depth
|
63
|
+
h = get_ssl(@url_public + "/bcorderbook?symbol=btcjpy") # the id of BTCJPY is 5.
|
64
|
+
{
|
65
|
+
"asks" => h["asks"].map{|a,b| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]}, # to_s でないと誤差が生じる
|
66
|
+
"bids" => h["bids"].map{|a,b| [BigDecimal.new(a.to_s), BigDecimal.new(b.to_s)]}, # to_s でないと誤差が生じる
|
67
|
+
"ltimestamp" => Time.now.to_i,
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def initialize_https(uri)
|
74
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
75
|
+
https.use_ssl = true
|
76
|
+
https.open_timeout = 5
|
77
|
+
https.read_timeout = 15
|
78
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
79
|
+
https.verify_depth = 5
|
80
|
+
https
|
81
|
+
end
|
82
|
+
|
83
|
+
# Connect to address via https, and return json response.
|
84
|
+
def get_ssl(address)
|
85
|
+
uri = URI.parse(address)
|
86
|
+
|
87
|
+
begin
|
88
|
+
https = initialize_https(uri)
|
89
|
+
https.start {|w|
|
90
|
+
response = w.get(uri.request_uri)
|
91
|
+
case response
|
92
|
+
when Net::HTTPSuccess
|
93
|
+
json = JSON.parse(response.body)
|
94
|
+
raise JSONException, response.body if json == nil
|
95
|
+
return json
|
96
|
+
else
|
97
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}."
|
98
|
+
end
|
99
|
+
}
|
100
|
+
rescue
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_nonce
|
106
|
+
pre_nonce = @@nonce
|
107
|
+
next_nonce = (Time.now.to_i) * 100
|
108
|
+
|
109
|
+
if next_nonce <= pre_nonce
|
110
|
+
@@nonce = pre_nonce + 1
|
111
|
+
else
|
112
|
+
@@nonce = next_nonce
|
113
|
+
end
|
114
|
+
|
115
|
+
return @@nonce
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_sign(req)
|
119
|
+
secret = @api_secret
|
120
|
+
text = req.body
|
121
|
+
|
122
|
+
OpenSSL::HMAC::hexdigest(OpenSSL::Digest.new('sha512'), secret, text)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Connect to address via https, and return json response.
|
126
|
+
def post_ssl(address, data={})
|
127
|
+
uri = URI.parse(address)
|
128
|
+
data["nonce"] = get_nonce
|
129
|
+
|
130
|
+
begin
|
131
|
+
req = Net::HTTP::Post.new(uri)
|
132
|
+
req.set_form_data(data)
|
133
|
+
req["Key"] = @api_key
|
134
|
+
req["Sign"] = get_sign(req)
|
135
|
+
|
136
|
+
https = initialize_https(uri)
|
137
|
+
https.start {|w|
|
138
|
+
response = w.request(req)
|
139
|
+
case response
|
140
|
+
when Net::HTTPSuccess
|
141
|
+
json = JSON.parse(response.body)
|
142
|
+
raise JSONException, response.body if json == nil
|
143
|
+
return json
|
144
|
+
else
|
145
|
+
raise ConnectionFailedException, "Failed to connect to #{@name}: " + response.value
|
146
|
+
end
|
147
|
+
}
|
148
|
+
rescue
|
149
|
+
raise
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Kaesen
|
2
|
+
|
3
|
+
# Exchange markets.
|
4
|
+
# @abstract
|
5
|
+
class Market
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@name = nil # [String] name of exchange market
|
10
|
+
@api_key = nil # [String]
|
11
|
+
@api_secret = nil # [String]
|
12
|
+
@url_public = nil # [String]
|
13
|
+
@url_private = nil # [String]
|
14
|
+
end
|
15
|
+
|
16
|
+
#############################################################
|
17
|
+
# API for public information
|
18
|
+
#############################################################
|
19
|
+
|
20
|
+
# Get ticker information.
|
21
|
+
# @abstract
|
22
|
+
# @return [hash] ticker
|
23
|
+
# ask: [BigDecimal] 最良売気配値
|
24
|
+
# bid: [BigDecimal] 最良買気配値
|
25
|
+
# last: [BigDecimal] 最近値(?用語要チェック), last price
|
26
|
+
# high: [BigDecimal] 高値
|
27
|
+
# low: [BigDecimal] 安値
|
28
|
+
# volume: [BigDecimal] 取引量
|
29
|
+
# ltimestamp: [int] Local Timestamp
|
30
|
+
def ticker
|
31
|
+
raise NotImplemented.new()
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get order book.
|
35
|
+
# @abstract
|
36
|
+
# @return [hash] array of market depth
|
37
|
+
# asks: [Array] 売りオーダー
|
38
|
+
# price : [BigDecimal]
|
39
|
+
# size : [BigDecimal]
|
40
|
+
# bids: [Array] 買いオーダー
|
41
|
+
# price : [BigDecimal]
|
42
|
+
# size : [BigDecimal]
|
43
|
+
# ltimestamp: [int] Local Timestamp
|
44
|
+
def depth
|
45
|
+
raise NotImplemented.new()
|
46
|
+
end
|
47
|
+
|
48
|
+
#############################################################
|
49
|
+
# API for private user data and trading
|
50
|
+
#############################################################
|
51
|
+
|
52
|
+
# Get account balance.
|
53
|
+
# @abstract
|
54
|
+
# @return [hash] account_balance_hash
|
55
|
+
# jpy: [hash]
|
56
|
+
# amount: [BigDecimal] 総日本円
|
57
|
+
# available: [BigDecimal] 取引可能な日本円
|
58
|
+
# btc [hash]
|
59
|
+
# amount: [BigDecimal] 総BTC
|
60
|
+
# available: [BigDecimal] 取引可能なBTC
|
61
|
+
# ltimestamp: [int] Local Timestamp
|
62
|
+
def balance
|
63
|
+
raise NotImplemented.new()
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get open orders.
|
67
|
+
# @abstract
|
68
|
+
# @return [Array] open_orders_array
|
69
|
+
# @return [hash] history_order_hash
|
70
|
+
# success: [bool]
|
71
|
+
# id: [String] order id in the market
|
72
|
+
# rate: [BigDecimal]
|
73
|
+
# amount: [BigDecimal]
|
74
|
+
# order_type: [String] "sell" or "buy"
|
75
|
+
# ltimestamp: [int] Local Timestamp
|
76
|
+
def opens
|
77
|
+
raise NotImplemented.new()
|
78
|
+
end
|
79
|
+
|
80
|
+
# Buy the amount of Bitcoin at the rate.
|
81
|
+
# 指数注文 買い.
|
82
|
+
# @abstract
|
83
|
+
# @param [BigDecimal] rate
|
84
|
+
# @param [BigDecimal] amount
|
85
|
+
# @return [hash] history_order_hash
|
86
|
+
# success: [bool]
|
87
|
+
# id: [String] order id in the market
|
88
|
+
# rate: [BigDecimal]
|
89
|
+
# amount: [BigDecimal]
|
90
|
+
# order_type: [String] "sell" or "buy"
|
91
|
+
# ltimestamp: [int] Local Timestamp
|
92
|
+
def buy(rate, amount=BigDecimal.new("0.0"))
|
93
|
+
raise NotImplemented.new()
|
94
|
+
end
|
95
|
+
|
96
|
+
# Buy the amount of Bitcoin from the market.
|
97
|
+
# 成行注文 買い.
|
98
|
+
# @abstract
|
99
|
+
# @param [BigDecimal] amount
|
100
|
+
# @return [hash] history_order_hash
|
101
|
+
# success: [bool]
|
102
|
+
# id: [String] order id in the market
|
103
|
+
# rate: [BigDecimal]
|
104
|
+
# amount: [BigDecimal]
|
105
|
+
# order_type: [String] "sell" or "buy"
|
106
|
+
# ltimestamp: [int] Local Timestamp
|
107
|
+
def market_buy(amount=BigDecimal.new("0.0"))
|
108
|
+
raise NotImplemented.new()
|
109
|
+
end
|
110
|
+
|
111
|
+
# Sell the amount of Bitcoin at the rate.
|
112
|
+
# 指数注文 売り.
|
113
|
+
# @abstract
|
114
|
+
# @param [BigDecimal] rate
|
115
|
+
# @param [BigDecimal] amount
|
116
|
+
# @return [hash] history_order_hash
|
117
|
+
# success: [String] "true" or "false"
|
118
|
+
# id: [String] order id in the market
|
119
|
+
# rate: [BigDecimal]
|
120
|
+
# amount: [BigDecimal]
|
121
|
+
# order_type: [String] "sell" or "buy"
|
122
|
+
# ltimestamp: [int] Local Timestamp
|
123
|
+
def sell(rate, amount=BigDecimal.new("0.0"))
|
124
|
+
raise NotImplemented.new()
|
125
|
+
end
|
126
|
+
|
127
|
+
# Sell the amount of Bitcoin to the market.
|
128
|
+
# 成行注文 売り.
|
129
|
+
# @abstract
|
130
|
+
# @param [BigDecimal] amount
|
131
|
+
# @return [hash] history_order_hash
|
132
|
+
# success: [bool]
|
133
|
+
# id: [String] order id in the market
|
134
|
+
# rate: [BigDecimal]
|
135
|
+
# amount: [BigDecimal]
|
136
|
+
# order_type: [String] "sell" or "buy"
|
137
|
+
# ltimestamp: [int] Local Timestamp
|
138
|
+
def market_sell(amount=BigDecimal.new("0.0"))
|
139
|
+
raise NotImplemented.new()
|
140
|
+
end
|
141
|
+
|
142
|
+
# Cancel an open order
|
143
|
+
# @abstract
|
144
|
+
# @param [int or string] order id
|
145
|
+
# @return [hash]
|
146
|
+
# success: [bool] status
|
147
|
+
def cancel(id)
|
148
|
+
raise NotImplemented.new()
|
149
|
+
end
|
150
|
+
|
151
|
+
# Cancel all open orders
|
152
|
+
# @abstract
|
153
|
+
# @return [array]
|
154
|
+
# success: [bool] status
|
155
|
+
def cancel_all
|
156
|
+
raise NotImplemented.new()
|
157
|
+
end
|
158
|
+
|
159
|
+
# Pretty printer for data including BigDecimal
|
160
|
+
# @param [any] data that may include BigDecimal
|
161
|
+
# @return [any] data that does not include BigDecimal
|
162
|
+
def self.unBigDecimal(x)
|
163
|
+
if x.is_a?(Array)
|
164
|
+
x.map{|y| unBigDecimal(y)}
|
165
|
+
elsif x.is_a?(Hash)
|
166
|
+
x.map{|k,v|
|
167
|
+
[k, unBigDecimal(v)]
|
168
|
+
}.to_h
|
169
|
+
elsif x.is_a?(BigDecimal)
|
170
|
+
x.to_f
|
171
|
+
else
|
172
|
+
x
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
# Check the API key and API secret key.
|
179
|
+
def have_key?
|
180
|
+
raise "Your #{@name} API key is not set" if @api_key.nil?
|
181
|
+
raise "Your #{@name} API secret is not set" if @api_secret.nil?
|
182
|
+
end
|
183
|
+
|
184
|
+
class ConnectionFailedException < StandardError; end
|
185
|
+
class APIErrorException < StandardError; end
|
186
|
+
class JSONException < StandardError; end
|
187
|
+
end
|
188
|
+
end
|