gate.rb 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/Gemfile +11 -0
- data/README.md +88 -0
- data/gate.rb.gemspec +28 -0
- data/lib/Gate/Client.rb +8 -0
- data/lib/Gate/Error.rb +30 -0
- data/lib/Gate/V4/Client.rb +254 -0
- data/lib/Gate/VERSION.rb +6 -0
- data/lib/Hash/stringify_values.rb +14 -0
- data/lib/Hash/x_www_form_urlencode.rb +7 -0
- data/lib/Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb +26 -0
- data/lib/Thoran/String/UrlEncode/url_encode.rb +26 -0
- data/lib/gate.rb +9 -0
- data/test/Gate/V4/Client/test_private_endpoints.rb +43 -0
- data/test/Gate/V4/Client/test_public_endpoints.rb +147 -0
- data/test/helper.rb +32 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 97a150ad280b246d59dce0e7e011ae8faeaf9f3c743b726ae931a7353457e5b3
|
4
|
+
data.tar.gz: a530909743923390245e633b04703c81fe9d78d10387118c959cea4d59a94e41
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e05641f6589dd90c24b86779b8845309da739e3fc662712c43b9ac49c00abd0097a45b17f77378ef94d943566da09b03519792e5b9d965d9e7ac953bdc6bd252
|
7
|
+
data.tar.gz: a40ba066ccc2aeeea27067601a0d91bd5f41ac5c5843c599efa58c69bd5b71bde598fec6979efdab6582d8ff634d1c02aa815d0de91050425ff90ef685e35cf4
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# gate.rb
|
2
|
+
|
3
|
+
Access the Gate API with Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'gate.rb'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```
|
22
|
+
$ gem install gate.rb
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
client = Gate::Client.new(
|
29
|
+
api_key: 'your_api_key',
|
30
|
+
api_secret: 'your_api_secret',
|
31
|
+
)
|
32
|
+
```
|
33
|
+
|
34
|
+
### Public API Methods
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Get all currencies
|
38
|
+
client.spot_currencies
|
39
|
+
|
40
|
+
# Get specific currency
|
41
|
+
client.spot_currencies('BTC')
|
42
|
+
|
43
|
+
# Get all symbol/currency pairs
|
44
|
+
client.currency_pairs
|
45
|
+
|
46
|
+
# Get one symbol/currency pair
|
47
|
+
client.currency_pairs('BTC_USDT')
|
48
|
+
|
49
|
+
# Get all ticker
|
50
|
+
client.spot_tickers
|
51
|
+
|
52
|
+
# Get ticker for one symbol/currency pair
|
53
|
+
client.spot_tickers(currency_pair: 'BTC_USDT')
|
54
|
+
|
55
|
+
# Get order book for a symbol/currency pair
|
56
|
+
client.spot_order_book(currency_pair: 'BTC_USDT')
|
57
|
+
|
58
|
+
# Get order book for a symbol/currency pair
|
59
|
+
client.spot_trades(currency_pair: 'BTC_USDT')
|
60
|
+
|
61
|
+
# Get OHLC for a symbol/currency pair
|
62
|
+
client.spot_candlesticks(currency_pair: 'BTC_USDT')
|
63
|
+
|
64
|
+
# Get OHLC for a symbol for specific period
|
65
|
+
client.spot_candlesticks('BTC_USDT', interval: '1d', start_time: 1648995780000, end_time: 1649082180000)
|
66
|
+
|
67
|
+
# Get server time
|
68
|
+
client.spot_time
|
69
|
+
```
|
70
|
+
|
71
|
+
### A Private API Method
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# Get the authenticated user's trades for specific symbol/currency pair
|
75
|
+
client.spot_orders(currency_pair: 'BTC_USDT')
|
76
|
+
```
|
77
|
+
|
78
|
+
## Contributing
|
79
|
+
|
80
|
+
1. Fork it (https://github.com/thoran/gate.rb/fork)
|
81
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
82
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
83
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
84
|
+
5. Create a new pull request
|
85
|
+
|
86
|
+
## License
|
87
|
+
|
88
|
+
The gem is available as open source under the terms of the [Ruby License](https://opensource.org/licenses/MIT).
|
data/gate.rb.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative './lib/Gate/VERSION'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'gate.rb'
|
5
|
+
|
6
|
+
spec.version = Gate::VERSION
|
7
|
+
spec.date = '2025-07-14'
|
8
|
+
|
9
|
+
spec.summary = "Access the Gate API with Ruby."
|
10
|
+
spec.description = "Access the Gate API with Ruby."
|
11
|
+
|
12
|
+
spec.author = 'thoran'
|
13
|
+
spec.email = 'code@thoran.com'
|
14
|
+
spec.homepage = 'http://github.com/thoran/gate.rb'
|
15
|
+
spec.license = 'Ruby'
|
16
|
+
|
17
|
+
spec.required_ruby_version = '>= 2.7'
|
18
|
+
|
19
|
+
spec.add_dependency('http.rb')
|
20
|
+
spec.files = [
|
21
|
+
'gate.rb.gemspec',
|
22
|
+
'Gemfile',
|
23
|
+
Dir['lib/**/*.rb'],
|
24
|
+
'README.md',
|
25
|
+
Dir['test/**/*.rb']
|
26
|
+
].flatten
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
end
|
data/lib/Gate/Client.rb
ADDED
data/lib/Gate/Error.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Gate/Error.rb
|
2
|
+
# Gate::Error
|
3
|
+
|
4
|
+
module Gate
|
5
|
+
class Error < RuntimeError
|
6
|
+
attr_reader\
|
7
|
+
:code,
|
8
|
+
:message,
|
9
|
+
:body
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{self.class} (#{@code}): #{@message}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def initialize(code:, message:, body:)
|
18
|
+
@code = code
|
19
|
+
@message = message
|
20
|
+
@body = body
|
21
|
+
super(message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class APIError < Error; end
|
26
|
+
class AuthenticationError < Error; end
|
27
|
+
class InvalidRequestError < Error; end
|
28
|
+
class NetworkError < Error; end
|
29
|
+
class RateLimitError < Error; end
|
30
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# Gate/V4/Client.rb
|
2
|
+
# Gate::V4::Client
|
3
|
+
|
4
|
+
# 20250714
|
5
|
+
# 0.1.0
|
6
|
+
|
7
|
+
# Notes:
|
8
|
+
# 1. API methods appear in the order in which they appear in the documentation.
|
9
|
+
|
10
|
+
gem 'http.rb'; require 'http.rb'
|
11
|
+
require 'json'
|
12
|
+
require 'openssl'
|
13
|
+
|
14
|
+
require 'Gate/Error'
|
15
|
+
require 'Hash/stringify_values'
|
16
|
+
require 'Hash/x_www_form_urlencode'
|
17
|
+
|
18
|
+
module Gate
|
19
|
+
module V4
|
20
|
+
class Client
|
21
|
+
|
22
|
+
API_HOST = 'api.gateio.ws'
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def path_prefix
|
26
|
+
'/api/v4'
|
27
|
+
end
|
28
|
+
end # class << self
|
29
|
+
|
30
|
+
# Public endpoints
|
31
|
+
|
32
|
+
def spot_currencies(currency = nil)
|
33
|
+
response = get(
|
34
|
+
path: "/spot/currencies/#{currency}"
|
35
|
+
)
|
36
|
+
handle_response(response)
|
37
|
+
end
|
38
|
+
|
39
|
+
def spot_currency_pairs(currency_pair = nil)
|
40
|
+
response = get(
|
41
|
+
path: "/spot/currency_pairs/#{currency_pair}"
|
42
|
+
)
|
43
|
+
handle_response(response)
|
44
|
+
end
|
45
|
+
|
46
|
+
def spot_tickers(currency_pair: nil, timezone: nil)
|
47
|
+
response = get(
|
48
|
+
path: '/spot/tickers',
|
49
|
+
args: {currency_pair: currency_pair, timezone: timezone}
|
50
|
+
)
|
51
|
+
handle_response(response)
|
52
|
+
end
|
53
|
+
|
54
|
+
def spot_order_book(
|
55
|
+
currency_pair:,
|
56
|
+
interval: nil,
|
57
|
+
limit: nil,
|
58
|
+
with_id: nil
|
59
|
+
)
|
60
|
+
response = get(
|
61
|
+
path: '/spot/order_book',
|
62
|
+
args: {
|
63
|
+
currency_pair: currency_pair,
|
64
|
+
interval: interval,
|
65
|
+
limit: limit,
|
66
|
+
with_id: with_id
|
67
|
+
}
|
68
|
+
)
|
69
|
+
handle_response(response)
|
70
|
+
end
|
71
|
+
|
72
|
+
def spot_trades(
|
73
|
+
currency_pair:,
|
74
|
+
limit: nil,
|
75
|
+
last_id: nil,
|
76
|
+
reverse: nil,
|
77
|
+
from: nil,
|
78
|
+
to: nil,
|
79
|
+
page: nil
|
80
|
+
)
|
81
|
+
response = get(
|
82
|
+
path: '/spot/trades',
|
83
|
+
args: {
|
84
|
+
currency_pair: currency_pair,
|
85
|
+
limit: limit,
|
86
|
+
last_id: last_id,
|
87
|
+
reverse: reverse,
|
88
|
+
from: from,
|
89
|
+
to: to,
|
90
|
+
page: page
|
91
|
+
}
|
92
|
+
)
|
93
|
+
handle_response(response)
|
94
|
+
end
|
95
|
+
|
96
|
+
def spot_candlesticks(
|
97
|
+
currency_pair:,
|
98
|
+
limit: nil,
|
99
|
+
from: nil,
|
100
|
+
to: nil,
|
101
|
+
interval: nil
|
102
|
+
)
|
103
|
+
response = get(
|
104
|
+
path: '/spot/candlesticks',
|
105
|
+
args: {
|
106
|
+
currency_pair: currency_pair,
|
107
|
+
limit: limit,
|
108
|
+
from: from,
|
109
|
+
to: to,
|
110
|
+
interval: interval
|
111
|
+
}
|
112
|
+
)
|
113
|
+
handle_response(response)
|
114
|
+
end
|
115
|
+
|
116
|
+
def spot_time
|
117
|
+
response = get(
|
118
|
+
path: '/spot/time'
|
119
|
+
)
|
120
|
+
handle_response(response)
|
121
|
+
end
|
122
|
+
|
123
|
+
def spot_orders(
|
124
|
+
text: nil,
|
125
|
+
currency_pair:,
|
126
|
+
type: nil,
|
127
|
+
account: nil,
|
128
|
+
side:,
|
129
|
+
amount:,
|
130
|
+
price: nil,
|
131
|
+
time_in_force: nil,
|
132
|
+
iceberg: nil
|
133
|
+
)
|
134
|
+
args = {
|
135
|
+
text: text,
|
136
|
+
currency_pair: currency_pair,
|
137
|
+
type: type,
|
138
|
+
account: account,
|
139
|
+
side: side,
|
140
|
+
amount: amount,
|
141
|
+
price: price,
|
142
|
+
time_in_force: time_in_force,
|
143
|
+
iceberg: iceberg
|
144
|
+
}.reject{|k,v| v.nil?}.stringify_values
|
145
|
+
response = post(
|
146
|
+
path: '/spot/orders',
|
147
|
+
args: args
|
148
|
+
)
|
149
|
+
handle_response(response)
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def initialize(api_key:, api_secret:)
|
155
|
+
@api_key = api_key.encode('UTF-8')
|
156
|
+
@api_secret = api_secret.encode('UTF-8')
|
157
|
+
end
|
158
|
+
|
159
|
+
def full_path(path)
|
160
|
+
self.class.path_prefix + path
|
161
|
+
end
|
162
|
+
|
163
|
+
def encoded_payload(args)
|
164
|
+
args.reject!{|k,v| v.nil?}
|
165
|
+
OpenSSL::Digest::SHA512.hexdigest(JSON.dump(args))
|
166
|
+
end
|
167
|
+
|
168
|
+
def timestamp
|
169
|
+
@timestamp ||= Time.now.to_i.to_s
|
170
|
+
end
|
171
|
+
|
172
|
+
def message(verb:, path:, args:)
|
173
|
+
query_string = (
|
174
|
+
case verb
|
175
|
+
when 'GET'
|
176
|
+
args.x_www_form_urlencode
|
177
|
+
when 'POST'
|
178
|
+
nil
|
179
|
+
else
|
180
|
+
raise "The verb, #{verb}, is not acceptable."
|
181
|
+
end
|
182
|
+
)
|
183
|
+
[verb, full_path(path), query_string, encoded_payload(args), timestamp].join("\n")
|
184
|
+
end
|
185
|
+
|
186
|
+
def signature(message)
|
187
|
+
OpenSSL::HMAC.hexdigest('SHA512', @api_secret, message)
|
188
|
+
end
|
189
|
+
|
190
|
+
def request_string(path)
|
191
|
+
"https://#{API_HOST}#{self.class.path_prefix}#{path}"
|
192
|
+
end
|
193
|
+
|
194
|
+
def headers(signature)
|
195
|
+
{
|
196
|
+
'Accept' => 'application/json',
|
197
|
+
'Content-Type' => 'application/json',
|
198
|
+
'KEY' => @api_key,
|
199
|
+
'SIGN' => signature,
|
200
|
+
'Timestamp' => timestamp,
|
201
|
+
}
|
202
|
+
end
|
203
|
+
|
204
|
+
def do_request(verb:, path:, args: {})
|
205
|
+
message = message(verb: verb, path: path, args: args)
|
206
|
+
signature = signature(message)
|
207
|
+
response = HTTP.send(verb.to_s.downcase, request_string(path), args, headers(signature))
|
208
|
+
@timestamp = nil
|
209
|
+
response
|
210
|
+
end
|
211
|
+
|
212
|
+
def get(path:, args: {})
|
213
|
+
do_request(verb: 'GET', path: path, args: args)
|
214
|
+
end
|
215
|
+
|
216
|
+
def post(path:, args: {})
|
217
|
+
do_request(verb: 'POST', path: path, args: args)
|
218
|
+
end
|
219
|
+
|
220
|
+
def handle_response(response)
|
221
|
+
if response.success?
|
222
|
+
JSON.parse(response.body)
|
223
|
+
else
|
224
|
+
case response.code.to_i
|
225
|
+
when 400
|
226
|
+
raise Gate::InvalidRequestError.new(
|
227
|
+
code: response.code,
|
228
|
+
message: response.message,
|
229
|
+
body: response.body
|
230
|
+
)
|
231
|
+
when 401
|
232
|
+
raise Gate::AuthenticationError.new(
|
233
|
+
code: response.code,
|
234
|
+
message: response.message,
|
235
|
+
body: response.body
|
236
|
+
)
|
237
|
+
when 429
|
238
|
+
raise Gate::RateLimitError.new(
|
239
|
+
code: response.code,
|
240
|
+
message: response.message,
|
241
|
+
body: response.body
|
242
|
+
)
|
243
|
+
else
|
244
|
+
raise Gate::APIError.new(
|
245
|
+
code: response.code,
|
246
|
+
message: response.message,
|
247
|
+
body: response.body
|
248
|
+
)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
data/lib/Gate/VERSION.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Thoran/Hash/XWwwFormUrlEncode/x_www_form_urlencode.rb
|
2
|
+
# Thoran::Hash::XWwwFormUrlEncode#x_www_form_urlencode
|
3
|
+
|
4
|
+
# 20241009
|
5
|
+
# 0.2.0
|
6
|
+
|
7
|
+
# Changes since 0.1:
|
8
|
+
# -/0: (The class name and the snake case name are consistent now.)
|
9
|
+
# 1. /XWWWFormUrlEncode/XWwwFormUrlEncode/
|
10
|
+
|
11
|
+
require 'Thoran/String/UrlEncode/url_encode'
|
12
|
+
|
13
|
+
module Thoran
|
14
|
+
module Hash
|
15
|
+
module XWwwFormUrlEncode
|
16
|
+
|
17
|
+
def x_www_form_url_encode(joiner = '&')
|
18
|
+
inject([]){|a,e| a << "#{e.first.to_s.url_encode}=#{e.last.to_s.url_encode}" unless e.last.nil?; a}.join(joiner)
|
19
|
+
end
|
20
|
+
alias_method :x_www_form_urlencode, :x_www_form_url_encode
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Hash.send(:include, Thoran::Hash::XWwwFormUrlEncode)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Thoran/String/UrlEncode/url_encode.rb
|
2
|
+
# Thoran::String::UrlEncode#url_encode
|
3
|
+
|
4
|
+
# 20160505
|
5
|
+
# 0.3.0
|
6
|
+
|
7
|
+
# Acknowledgements: I've simply ripped off and refashioned the code from the CGI module!...
|
8
|
+
|
9
|
+
# Changes since 0.2:
|
10
|
+
# 1. + Thoran namespace.
|
11
|
+
|
12
|
+
module Thoran
|
13
|
+
module String
|
14
|
+
module UrlEncode
|
15
|
+
|
16
|
+
def url_encode
|
17
|
+
self.gsub(/([^ a-zA-Z0-9_.-]+)/n) do
|
18
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
19
|
+
end.tr(' ', '+')
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
String.send(:include, Thoran::String::UrlEncode)
|
data/lib/gate.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative '../../../helper'
|
2
|
+
|
3
|
+
describe Gate::V4::Client do
|
4
|
+
let(:api_key){ENV.fetch('GATE_API_KEY', '<API_KEY>')}
|
5
|
+
let(:api_secret){ENV.fetch('GATE_API_SECRET', '<API_SECRET>')}
|
6
|
+
|
7
|
+
subject do
|
8
|
+
Gate::V4::Client.new(
|
9
|
+
api_key: api_key,
|
10
|
+
api_secret: api_secret
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#spot_orders" do
|
15
|
+
let(:amount){0.0001}
|
16
|
+
let(:price){100_000}
|
17
|
+
let(:side){'buy'}
|
18
|
+
|
19
|
+
context "success" do
|
20
|
+
it "fetches my orders for a specific market" do
|
21
|
+
VCR.use_cassette('v4/spot/orders') do
|
22
|
+
response = subject.spot_orders(currency_pair: 'BTC_USDT', side: side, amount: amount, time_in_force: 'gtc', price: price)
|
23
|
+
_(response).must_be_kind_of(Hash)
|
24
|
+
assert(response['id'])
|
25
|
+
assert(response['create_time'])
|
26
|
+
_(response['amount']).must_equal(amount.to_s)
|
27
|
+
_(response['price']).must_equal(price.to_s)
|
28
|
+
_(response['side']).must_equal(side)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "invalid key" do
|
34
|
+
it "fetches my orders for a specific market" do
|
35
|
+
VCR.use_cassette('v4/spot/orders_with_an_invalid_key') do
|
36
|
+
assert_raises do
|
37
|
+
subject.spot_orders(currency_pair: 'BTC_USDT', side: side, amount: amount, time_in_force: 'gtc', price: price)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require_relative '../../../helper'
|
2
|
+
|
3
|
+
describe Gate::V4::Client do
|
4
|
+
let(:api_key){ENV.fetch('GATE_API_KEY', '<API_KEY>')}
|
5
|
+
let(:api_secret){ENV.fetch('GATE_API_SECRET', '<API_SECRET>')}
|
6
|
+
|
7
|
+
subject do
|
8
|
+
Gate::V4::Client.new(
|
9
|
+
api_key: api_key,
|
10
|
+
api_secret: api_secret
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#spot_currencies" do
|
15
|
+
context "no currency specified" do
|
16
|
+
it "fetches all currencies" do
|
17
|
+
VCR.use_cassette('v4/spot/currencies') do
|
18
|
+
response = subject.spot_currencies
|
19
|
+
_(response).must_be_kind_of(Array)
|
20
|
+
assert(response.size > 4000)
|
21
|
+
_(response[0]['currency']).must_equal('BBC')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "currency specified" do
|
27
|
+
it "fetches one currency" do
|
28
|
+
VCR.use_cassette('v4/spot/currencies_btc') do
|
29
|
+
response = subject.spot_currencies('BTC')
|
30
|
+
_(response).must_be_kind_of(Hash)
|
31
|
+
_(response.size).must_equal(10)
|
32
|
+
_(response['currency']).must_equal('BTC')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#spot_currency_pairs" do
|
39
|
+
context "no pair specified" do
|
40
|
+
it "fetches data for all markets" do
|
41
|
+
VCR.use_cassette('v4/spot/currency_pairs') do
|
42
|
+
response = subject.spot_currency_pairs
|
43
|
+
_(response).must_be_kind_of(Array)
|
44
|
+
assert(response.size > 2000)
|
45
|
+
_(response[0]['id']).must_equal('ALEPH_USDT')
|
46
|
+
_(response[0]['base']).must_equal('ALEPH')
|
47
|
+
_(response[0]['quote']).must_equal('USDT')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "pair specified" do
|
53
|
+
it "fetches data for one market" do
|
54
|
+
VCR.use_cassette('v4/spot/currency_pairs_btc') do
|
55
|
+
response = subject.spot_currency_pairs('BTC_USDT')
|
56
|
+
_(response).must_be_kind_of(Hash)
|
57
|
+
_(response.size).must_equal(18)
|
58
|
+
_(response['id']).must_equal('BTC_USDT')
|
59
|
+
_(response['base']).must_equal('BTC')
|
60
|
+
_(response['quote']).must_equal('USDT')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#spot_tickers" do
|
67
|
+
context "no ticker specified" do
|
68
|
+
it "fetches tickers for all markets" do
|
69
|
+
VCR.use_cassette('v4/spot/tickers') do
|
70
|
+
response = subject.spot_tickers
|
71
|
+
_(response).must_be_kind_of(Array)
|
72
|
+
assert(response.size > 2000)
|
73
|
+
_(response[0]['currency_pair']).must_equal('BAND_USDT')
|
74
|
+
assert(response[0]['last'].match(/\d+\.\d*/))
|
75
|
+
assert(response[0]['change_percentage'].match(/\d+\.\d*/))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "ticker specified" do
|
81
|
+
it "fetches ticker for a specific market" do
|
82
|
+
VCR.use_cassette('v4/spot/tickers_btc') do
|
83
|
+
response = subject.spot_tickers(currency_pair: 'BTC_USDT')
|
84
|
+
_(response).must_be_kind_of(Array)
|
85
|
+
_(response[0]['currency_pair']).must_equal('BTC_USDT')
|
86
|
+
assert(response[0]['last'].match(/\d+\.\d*/))
|
87
|
+
assert(response[0]['change_percentage'].match(/\d+\.\d*/))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#spot_order_book" do
|
94
|
+
it "retrieves order book for a specific market" do
|
95
|
+
VCR.use_cassette('v4/spot/order_book') do
|
96
|
+
response = subject.spot_order_book(currency_pair:'BTC_USDT', limit: 10)
|
97
|
+
_(response).must_be_kind_of(Hash)
|
98
|
+
_(response['asks'].size).must_equal(10)
|
99
|
+
_(response['bids'].size).must_equal(10)
|
100
|
+
_(response['asks']).must_be_kind_of(Array)
|
101
|
+
_(response['bids']).must_be_kind_of(Array)
|
102
|
+
assert(response['asks'].first.first.match(/\d+\.*\d*/))
|
103
|
+
assert(response['asks'].first.last.match(/\d+\.*\d*/))
|
104
|
+
assert(response['bids'].first.first.match(/\d+\.*\d*/))
|
105
|
+
assert(response['bids'].first.last.match(/\d+\.*\d*/))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#spot_trades" do
|
111
|
+
it "fetches recent trades for a given market" do
|
112
|
+
VCR.use_cassette('v4/spot/trades') do
|
113
|
+
response = subject.spot_trades(currency_pair: 'BTC_USDT', limit: 10)
|
114
|
+
_(response).must_be_kind_of(Array)
|
115
|
+
_(response.size).must_equal(10)
|
116
|
+
assert(response[0]['id'].match(/\d+\.*\d*/))
|
117
|
+
assert(response[0]['side'] == 'buy' || response[0]['side'] == 'sell')
|
118
|
+
assert(response[0]['amount'].match(/\d+\.*\d*/))
|
119
|
+
assert(response[0]['price'].match(/\d+\.*\d*/))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#spot_candlesticks" do
|
125
|
+
it "fetches candlestick data for a specific market" do
|
126
|
+
VCR.use_cassette('v4/spot/candlesticks') do
|
127
|
+
response = subject.spot_candlesticks(currency_pair: 'BTC_USDT', interval: '1h', limit: 10)
|
128
|
+
_(response).must_be_kind_of(Array)
|
129
|
+
_(response.size).must_equal(10)
|
130
|
+
_(response[0].size).must_equal(8)
|
131
|
+
assert(response[0][0].match(/\d+\.*\d*/))
|
132
|
+
assert(response[0][1].match(/\d+\.*\d*/)) # open
|
133
|
+
assert(response[0][4].match(/\d+\.*\d*/)) # close
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "#spot_time" do
|
139
|
+
it "fetches candlestick data for a specific market" do
|
140
|
+
VCR.use_cassette('v4/spot/time') do
|
141
|
+
response = subject.spot_time
|
142
|
+
_(response).must_be_kind_of(Hash)
|
143
|
+
assert(response['server_time'].to_s.match(/\d+\.*\d*/))
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/spec'
|
3
|
+
require 'minitest-spec-context'
|
4
|
+
require 'vcr'
|
5
|
+
require 'webmock'
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
8
|
+
require 'gate'
|
9
|
+
|
10
|
+
VCR.configure do |config|
|
11
|
+
config.cassette_library_dir = 'test/fixtures/vcr_cassettes'
|
12
|
+
|
13
|
+
config.hook_into :webmock
|
14
|
+
|
15
|
+
config.filter_sensitive_data('<API_KEY>'){ENV['GATE_API_KEY']}
|
16
|
+
config.filter_sensitive_data('<API_SECRET>'){ENV['GATE_API_SECRET'] }
|
17
|
+
|
18
|
+
# Allow localhost connections for debugging
|
19
|
+
config.ignore_localhost = true
|
20
|
+
|
21
|
+
config.default_cassette_options = {
|
22
|
+
record: :new_episodes,
|
23
|
+
match_requests_on: [:method, :uri]
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
class Minitest::Test
|
28
|
+
def before_setup
|
29
|
+
super
|
30
|
+
Gate.reset_configuration!
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gate.rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- thoran
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-07-14 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: http.rb
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
description: Access the Gate API with Ruby.
|
27
|
+
email: code@thoran.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- Gemfile
|
33
|
+
- README.md
|
34
|
+
- gate.rb.gemspec
|
35
|
+
- lib/Gate/Client.rb
|
36
|
+
- lib/Gate/Error.rb
|
37
|
+
- lib/Gate/V4/Client.rb
|
38
|
+
- lib/Gate/VERSION.rb
|
39
|
+
- lib/Hash/stringify_values.rb
|
40
|
+
- lib/Hash/x_www_form_urlencode.rb
|
41
|
+
- lib/Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb
|
42
|
+
- lib/Thoran/String/UrlEncode/url_encode.rb
|
43
|
+
- lib/gate.rb
|
44
|
+
- test/Gate/V4/Client/test_private_endpoints.rb
|
45
|
+
- test/Gate/V4/Client/test_public_endpoints.rb
|
46
|
+
- test/helper.rb
|
47
|
+
homepage: http://github.com/thoran/gate.rb
|
48
|
+
licenses:
|
49
|
+
- Ruby
|
50
|
+
metadata: {}
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '2.7'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubygems_version: 3.6.9
|
66
|
+
specification_version: 4
|
67
|
+
summary: Access the Gate API with Ruby.
|
68
|
+
test_files: []
|