buda_api 1.0.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/.gitignore +57 -0
- data/.rspec +3 -0
- data/.rubocop.yml +49 -0
- data/CHANGELOG.md +53 -0
- data/Gemfile +11 -0
- data/LICENSE +21 -0
- data/README.md +526 -0
- data/Rakefile +37 -0
- data/buda_api.gemspec +38 -0
- data/examples/.env.example +11 -0
- data/examples/authenticated_api_example.rb +213 -0
- data/examples/error_handling_example.rb +221 -0
- data/examples/public_api_example.rb +142 -0
- data/examples/trading_bot_example.rb +279 -0
- data/lib/buda_api/authenticated_client.rb +403 -0
- data/lib/buda_api/client.rb +248 -0
- data/lib/buda_api/constants.rb +163 -0
- data/lib/buda_api/errors.rb +60 -0
- data/lib/buda_api/logger.rb +99 -0
- data/lib/buda_api/models.rb +365 -0
- data/lib/buda_api/public_client.rb +231 -0
- data/lib/buda_api/version.rb +5 -0
- data/lib/buda_api.rb +81 -0
- metadata +194 -0
@@ -0,0 +1,365 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
module BudaApi
|
7
|
+
# Data models for API responses
|
8
|
+
module Models
|
9
|
+
# Base model class with common functionality
|
10
|
+
class BaseModel
|
11
|
+
def initialize(data = {})
|
12
|
+
@data = data.is_a?(Hash) ? data : {}
|
13
|
+
@raw_data = @data.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
# Access to raw API response data
|
17
|
+
def raw
|
18
|
+
@raw_data
|
19
|
+
end
|
20
|
+
|
21
|
+
# Convert to hash
|
22
|
+
def to_h
|
23
|
+
@data
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convert to JSON
|
27
|
+
def to_json(*args)
|
28
|
+
@data.to_json(*args)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_datetime(datetime_string)
|
34
|
+
return nil if datetime_string.nil? || datetime_string.empty?
|
35
|
+
|
36
|
+
Time.parse(datetime_string)
|
37
|
+
rescue ArgumentError
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_amount(amount_data)
|
42
|
+
return nil unless amount_data.is_a?(Hash)
|
43
|
+
|
44
|
+
Amount.new(amount_data)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Amount model for currency amounts
|
49
|
+
class Amount < BaseModel
|
50
|
+
def initialize(data)
|
51
|
+
super(data)
|
52
|
+
@amount = data["amount"]&.to_f || 0.0
|
53
|
+
@currency = data["currency"]
|
54
|
+
end
|
55
|
+
|
56
|
+
attr_reader :amount, :currency
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
"#{@amount} #{@currency}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def ==(other)
|
63
|
+
other.is_a?(Amount) &&
|
64
|
+
@amount == other.amount &&
|
65
|
+
@currency == other.currency
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Market model
|
70
|
+
class Market < BaseModel
|
71
|
+
def initialize(data)
|
72
|
+
super(data)
|
73
|
+
@id = data["id"]
|
74
|
+
@name = data["name"]
|
75
|
+
@base_currency = data["base_currency"]
|
76
|
+
@quote_currency = data["quote_currency"]
|
77
|
+
@minimum_order_amount = parse_amount(data["minimum_order_amount"])
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader :id, :name, :base_currency, :quote_currency, :minimum_order_amount
|
81
|
+
end
|
82
|
+
|
83
|
+
# Ticker model
|
84
|
+
class Ticker < BaseModel
|
85
|
+
def initialize(data)
|
86
|
+
super(data)
|
87
|
+
@last_price = parse_amount(data["last_price"])
|
88
|
+
@min_ask = parse_amount(data["min_ask"])
|
89
|
+
@max_bid = parse_amount(data["max_bid"])
|
90
|
+
@volume = parse_amount(data["volume"])
|
91
|
+
@price_variation_24h = data["price_variation_24h"]&.to_f
|
92
|
+
@price_variation_7d = data["price_variation_7d"]&.to_f
|
93
|
+
end
|
94
|
+
|
95
|
+
attr_reader :last_price, :min_ask, :max_bid, :volume,
|
96
|
+
:price_variation_24h, :price_variation_7d
|
97
|
+
end
|
98
|
+
|
99
|
+
# Order book entry model
|
100
|
+
class OrderBookEntry < BaseModel
|
101
|
+
def initialize(data)
|
102
|
+
super(data)
|
103
|
+
if data.is_a?(Array) && data.length >= 2
|
104
|
+
@price = data[0].to_f
|
105
|
+
@amount = data[1].to_f
|
106
|
+
else
|
107
|
+
@price = data["price"]&.to_f || 0.0
|
108
|
+
@amount = data["amount"]&.to_f || 0.0
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
attr_reader :price, :amount
|
113
|
+
|
114
|
+
def total
|
115
|
+
@price * @amount
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Order book model
|
120
|
+
class OrderBook < BaseModel
|
121
|
+
def initialize(data)
|
122
|
+
super(data)
|
123
|
+
@asks = (data["asks"] || []).map { |entry| OrderBookEntry.new(entry) }
|
124
|
+
@bids = (data["bids"] || []).map { |entry| OrderBookEntry.new(entry) }
|
125
|
+
end
|
126
|
+
|
127
|
+
attr_reader :asks, :bids
|
128
|
+
|
129
|
+
def best_ask
|
130
|
+
@asks.first
|
131
|
+
end
|
132
|
+
|
133
|
+
def best_bid
|
134
|
+
@bids.first
|
135
|
+
end
|
136
|
+
|
137
|
+
def spread
|
138
|
+
return nil unless best_ask && best_bid
|
139
|
+
|
140
|
+
best_ask.price - best_bid.price
|
141
|
+
end
|
142
|
+
|
143
|
+
def spread_percentage
|
144
|
+
return nil unless best_ask && best_bid && best_bid.price > 0
|
145
|
+
|
146
|
+
((best_ask.price - best_bid.price) / best_bid.price * 100).round(4)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Trade model
|
151
|
+
class Trade < BaseModel
|
152
|
+
def initialize(data)
|
153
|
+
super(data)
|
154
|
+
@timestamp = parse_datetime(data["timestamp"]) || Time.at(data["timestamp"].to_i) if data["timestamp"]
|
155
|
+
@direction = data["direction"]
|
156
|
+
@price = parse_amount(data["price"])
|
157
|
+
@amount = parse_amount(data["amount"])
|
158
|
+
@market_id = data["market_id"]
|
159
|
+
end
|
160
|
+
|
161
|
+
attr_reader :timestamp, :direction, :price, :amount, :market_id
|
162
|
+
end
|
163
|
+
|
164
|
+
# Trades collection model
|
165
|
+
class Trades < BaseModel
|
166
|
+
def initialize(data)
|
167
|
+
super(data)
|
168
|
+
@trades = (data["trades"] || []).map { |trade_data| Trade.new(trade_data) }
|
169
|
+
@last_timestamp = data["last_timestamp"]
|
170
|
+
end
|
171
|
+
|
172
|
+
attr_reader :trades, :last_timestamp
|
173
|
+
|
174
|
+
def count
|
175
|
+
@trades.length
|
176
|
+
end
|
177
|
+
|
178
|
+
def each(&block)
|
179
|
+
@trades.each(&block)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Balance model
|
184
|
+
class Balance < BaseModel
|
185
|
+
def initialize(data)
|
186
|
+
super(data)
|
187
|
+
@id = data["id"]
|
188
|
+
@account_id = data["account_id"]
|
189
|
+
@amount = parse_amount(data["amount"])
|
190
|
+
@available_amount = parse_amount(data["available_amount"])
|
191
|
+
@frozen_amount = parse_amount(data["frozen_amount"])
|
192
|
+
@pending_withdraw_amount = parse_amount(data["pending_withdraw_amount"])
|
193
|
+
end
|
194
|
+
|
195
|
+
attr_reader :id, :account_id, :amount, :available_amount,
|
196
|
+
:frozen_amount, :pending_withdraw_amount
|
197
|
+
|
198
|
+
def currency
|
199
|
+
@amount&.currency
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# Order model
|
204
|
+
class Order < BaseModel
|
205
|
+
def initialize(data)
|
206
|
+
super(data)
|
207
|
+
@id = data["id"]
|
208
|
+
@account_id = data["account_id"]
|
209
|
+
@amount = parse_amount(data["amount"])
|
210
|
+
@created_at = parse_datetime(data["created_at"])
|
211
|
+
@fee_currency = data["fee_currency"]
|
212
|
+
@limit = parse_amount(data["limit"])
|
213
|
+
@market_id = data["market_id"]
|
214
|
+
@original_amount = parse_amount(data["original_amount"])
|
215
|
+
@paid_fee = parse_amount(data["paid_fee"])
|
216
|
+
@price_type = data["price_type"]
|
217
|
+
@state = data["state"]
|
218
|
+
@total_exchanged = parse_amount(data["total_exchanged"])
|
219
|
+
@traded_amount = parse_amount(data["traded_amount"])
|
220
|
+
@type = data["type"]
|
221
|
+
end
|
222
|
+
|
223
|
+
attr_reader :id, :account_id, :amount, :created_at, :fee_currency,
|
224
|
+
:limit, :market_id, :original_amount, :paid_fee, :price_type,
|
225
|
+
:state, :total_exchanged, :traded_amount, :type
|
226
|
+
|
227
|
+
def filled_percentage
|
228
|
+
return 0 unless @original_amount&.amount && @original_amount.amount > 0
|
229
|
+
|
230
|
+
((@traded_amount&.amount || 0) / @original_amount.amount * 100).round(2)
|
231
|
+
end
|
232
|
+
|
233
|
+
def is_filled?
|
234
|
+
@state == "traded"
|
235
|
+
end
|
236
|
+
|
237
|
+
def is_active?
|
238
|
+
%w[received pending].include?(@state)
|
239
|
+
end
|
240
|
+
|
241
|
+
def is_cancelled?
|
242
|
+
%w[canceled canceling].include?(@state)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Quotation model
|
247
|
+
class Quotation < BaseModel
|
248
|
+
def initialize(data)
|
249
|
+
super(data)
|
250
|
+
@type = data["type"]
|
251
|
+
@reverse_amount = parse_amount(data["reverse_amount"])
|
252
|
+
@amount = parse_amount(data["amount"])
|
253
|
+
@base_balance_change = parse_amount(data["base_balance_change"])
|
254
|
+
@quote_balance_change = parse_amount(data["quote_balance_change"])
|
255
|
+
@fee = parse_amount(data["fee"])
|
256
|
+
end
|
257
|
+
|
258
|
+
attr_reader :type, :reverse_amount, :amount, :base_balance_change,
|
259
|
+
:quote_balance_change, :fee
|
260
|
+
end
|
261
|
+
|
262
|
+
# Withdrawal model
|
263
|
+
class Withdrawal < BaseModel
|
264
|
+
def initialize(data)
|
265
|
+
super(data)
|
266
|
+
@id = data["id"]
|
267
|
+
@created_at = parse_datetime(data["created_at"])
|
268
|
+
@amount = parse_amount(data["amount"])
|
269
|
+
@fee = parse_amount(data["fee"])
|
270
|
+
@currency = data["currency"]
|
271
|
+
@state = data["state"]
|
272
|
+
@withdrawal_data = data["withdrawal_data"]
|
273
|
+
end
|
274
|
+
|
275
|
+
attr_reader :id, :created_at, :amount, :fee, :currency, :state, :withdrawal_data
|
276
|
+
|
277
|
+
def target_address
|
278
|
+
@withdrawal_data&.dig("target_address")
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Deposit model
|
283
|
+
class Deposit < BaseModel
|
284
|
+
def initialize(data)
|
285
|
+
super(data)
|
286
|
+
@id = data["id"]
|
287
|
+
@created_at = parse_datetime(data["created_at"])
|
288
|
+
@amount = parse_amount(data["amount"])
|
289
|
+
@currency = data["currency"]
|
290
|
+
@state = data["state"]
|
291
|
+
@deposit_data = data["deposit_data"]
|
292
|
+
end
|
293
|
+
|
294
|
+
attr_reader :id, :created_at, :amount, :currency, :state, :deposit_data
|
295
|
+
|
296
|
+
def address
|
297
|
+
@deposit_data&.dig("address")
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Pagination metadata
|
302
|
+
class PaginationMeta < BaseModel
|
303
|
+
def initialize(data)
|
304
|
+
super(data)
|
305
|
+
@current_page = data["current_page"]&.to_i
|
306
|
+
@total_count = data["total_count"]&.to_i
|
307
|
+
@total_pages = data["total_pages"]&.to_i
|
308
|
+
end
|
309
|
+
|
310
|
+
attr_reader :current_page, :total_count, :total_pages
|
311
|
+
|
312
|
+
def has_next_page?
|
313
|
+
@current_page && @total_pages && @current_page < @total_pages
|
314
|
+
end
|
315
|
+
|
316
|
+
def has_previous_page?
|
317
|
+
@current_page && @current_page > 1
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Paginated collection of orders
|
322
|
+
class OrderPages < BaseModel
|
323
|
+
def initialize(orders_data, meta_data)
|
324
|
+
@orders = orders_data.map { |order| Order.new(order) }
|
325
|
+
@meta = PaginationMeta.new(meta_data || {})
|
326
|
+
end
|
327
|
+
|
328
|
+
attr_reader :orders, :meta
|
329
|
+
|
330
|
+
def count
|
331
|
+
@orders.length
|
332
|
+
end
|
333
|
+
|
334
|
+
def each(&block)
|
335
|
+
@orders.each(&block)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Average price report entry
|
340
|
+
class AveragePrice < BaseModel
|
341
|
+
def initialize(data)
|
342
|
+
super(data)
|
343
|
+
@timestamp = Time.at(data["timestamp"].to_i) if data["timestamp"]
|
344
|
+
@average = data["average"]&.to_f
|
345
|
+
end
|
346
|
+
|
347
|
+
attr_reader :timestamp, :average
|
348
|
+
end
|
349
|
+
|
350
|
+
# Candlestick report entry
|
351
|
+
class Candlestick < BaseModel
|
352
|
+
def initialize(data)
|
353
|
+
super(data)
|
354
|
+
@timestamp = Time.at(data["timestamp"].to_i) if data["timestamp"]
|
355
|
+
@open = data["open"]&.to_f
|
356
|
+
@close = data["close"]&.to_f
|
357
|
+
@high = data["high"]&.to_f
|
358
|
+
@low = data["low"]&.to_f
|
359
|
+
@volume = data["volume"]&.to_f
|
360
|
+
end
|
361
|
+
|
362
|
+
attr_reader :timestamp, :open, :close, :high, :low, :volume
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BudaApi
|
4
|
+
# Public API client for endpoints that don't require authentication
|
5
|
+
class PublicClient < Client
|
6
|
+
include Models
|
7
|
+
|
8
|
+
# Get all available markets
|
9
|
+
#
|
10
|
+
# @return [Array<Market>] list of available markets
|
11
|
+
# @example
|
12
|
+
# client = BudaApi::PublicClient.new
|
13
|
+
# markets = client.markets
|
14
|
+
# markets.each { |market| puts "#{market.id}: #{market.name}" }
|
15
|
+
def markets
|
16
|
+
BudaApi::Logger.info("Fetching all markets")
|
17
|
+
|
18
|
+
response = get("markets")
|
19
|
+
markets_data = response["markets"] || []
|
20
|
+
|
21
|
+
markets = markets_data.map { |market_data| Market.new(market_data) }
|
22
|
+
BudaApi::Logger.info("Retrieved #{markets.length} markets")
|
23
|
+
|
24
|
+
markets
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get details for a specific market
|
28
|
+
#
|
29
|
+
# @param market_id [String] market identifier (e.g., "BTC-CLP")
|
30
|
+
# @return [Market] market details
|
31
|
+
# @example
|
32
|
+
# client = BudaApi::PublicClient.new
|
33
|
+
# market = client.market_details("BTC-CLP")
|
34
|
+
# puts "Minimum order: #{market.minimum_order_amount}"
|
35
|
+
def market_details(market_id)
|
36
|
+
validate_required_params({ market_id: market_id }, [:market_id])
|
37
|
+
validate_param_values({ market_id: market_id }, { market_id: Market::ALL })
|
38
|
+
|
39
|
+
BudaApi::Logger.info("Fetching market details for #{market_id}")
|
40
|
+
|
41
|
+
response = get("markets/#{market_id}")
|
42
|
+
Market.new(response["market"])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get ticker information for a market
|
46
|
+
#
|
47
|
+
# @param market_id [String] market identifier
|
48
|
+
# @return [Ticker] current ticker information
|
49
|
+
# @example
|
50
|
+
# client = BudaApi::PublicClient.new
|
51
|
+
# ticker = client.ticker("BTC-CLP")
|
52
|
+
# puts "Last price: #{ticker.last_price}"
|
53
|
+
# puts "24h change: #{ticker.price_variation_24h}%"
|
54
|
+
def ticker(market_id)
|
55
|
+
validate_required_params({ market_id: market_id }, [:market_id])
|
56
|
+
validate_param_values({ market_id: market_id }, { market_id: Market::ALL })
|
57
|
+
|
58
|
+
BudaApi::Logger.info("Fetching ticker for #{market_id}")
|
59
|
+
|
60
|
+
response = get("markets/#{market_id}/ticker")
|
61
|
+
Ticker.new(response["ticker"])
|
62
|
+
end
|
63
|
+
|
64
|
+
# Get order book for a market
|
65
|
+
#
|
66
|
+
# @param market_id [String] market identifier
|
67
|
+
# @return [OrderBook] current order book
|
68
|
+
# @example
|
69
|
+
# client = BudaApi::PublicClient.new
|
70
|
+
# order_book = client.order_book("BTC-CLP")
|
71
|
+
# puts "Best ask: #{order_book.best_ask.price}"
|
72
|
+
# puts "Best bid: #{order_book.best_bid.price}"
|
73
|
+
# puts "Spread: #{order_book.spread_percentage}%"
|
74
|
+
def order_book(market_id)
|
75
|
+
validate_required_params({ market_id: market_id }, [:market_id])
|
76
|
+
validate_param_values({ market_id: market_id }, { market_id: Market::ALL })
|
77
|
+
|
78
|
+
BudaApi::Logger.info("Fetching order book for #{market_id}")
|
79
|
+
|
80
|
+
response = get("markets/#{market_id}/order_book")
|
81
|
+
OrderBook.new(response["order_book"])
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get recent trades for a market
|
85
|
+
#
|
86
|
+
# @param market_id [String] market identifier
|
87
|
+
# @param timestamp [Integer, nil] trades after this timestamp
|
88
|
+
# @param limit [Integer, nil] maximum number of trades to return
|
89
|
+
# @return [Trades] collection of recent trades
|
90
|
+
# @example
|
91
|
+
# client = BudaApi::PublicClient.new
|
92
|
+
# trades = client.trades("BTC-CLP", limit: 10)
|
93
|
+
# trades.each { |trade| puts "#{trade.amount} at #{trade.price}" }
|
94
|
+
def trades(market_id, timestamp: nil, limit: nil)
|
95
|
+
validate_required_params({ market_id: market_id }, [:market_id])
|
96
|
+
validate_param_values({ market_id: market_id }, { market_id: Market::ALL })
|
97
|
+
|
98
|
+
params = normalize_params({
|
99
|
+
timestamp: timestamp,
|
100
|
+
limit: limit
|
101
|
+
})
|
102
|
+
|
103
|
+
BudaApi::Logger.info("Fetching trades for #{market_id} with params: #{params}")
|
104
|
+
|
105
|
+
response = get("markets/#{market_id}/trades", params)
|
106
|
+
Trades.new(response["trades"])
|
107
|
+
end
|
108
|
+
|
109
|
+
# Get a quotation for a potential trade
|
110
|
+
#
|
111
|
+
# @param market_id [String] market identifier
|
112
|
+
# @param quotation_type [String] type of quotation
|
113
|
+
# @param amount [Float] amount for quotation
|
114
|
+
# @param limit [Float, nil] limit price for limit quotations
|
115
|
+
# @return [Quotation] quotation details
|
116
|
+
# @example
|
117
|
+
# client = BudaApi::PublicClient.new
|
118
|
+
# # Get quotation for buying 0.1 BTC at market price
|
119
|
+
# quote = client.quotation("BTC-CLP", "bid_given_size", 0.1)
|
120
|
+
# puts "You would pay: #{quote.quote_balance_change}"
|
121
|
+
def quotation(market_id, quotation_type, amount, limit: nil)
|
122
|
+
validate_required_params({
|
123
|
+
market_id: market_id,
|
124
|
+
quotation_type: quotation_type,
|
125
|
+
amount: amount
|
126
|
+
}, [:market_id, :quotation_type, :amount])
|
127
|
+
|
128
|
+
validate_param_values({
|
129
|
+
market_id: market_id,
|
130
|
+
quotation_type: quotation_type
|
131
|
+
}, {
|
132
|
+
market_id: Market::ALL,
|
133
|
+
quotation_type: QuotationType::ALL
|
134
|
+
})
|
135
|
+
|
136
|
+
quotation_payload = {
|
137
|
+
type: quotation_type,
|
138
|
+
amount: amount.to_s
|
139
|
+
}
|
140
|
+
quotation_payload[:limit] = limit.to_s if limit
|
141
|
+
|
142
|
+
BudaApi::Logger.info("Getting quotation for #{market_id}: #{quotation_type} #{amount}")
|
143
|
+
|
144
|
+
response = post("markets/#{market_id}/quotations", body: { quotation: quotation_payload })
|
145
|
+
Quotation.new(response["quotation"])
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get a market quotation (no limit price)
|
149
|
+
#
|
150
|
+
# @param market_id [String] market identifier
|
151
|
+
# @param quotation_type [String] type of quotation
|
152
|
+
# @param amount [Float] amount for quotation
|
153
|
+
# @return [Quotation] market quotation details
|
154
|
+
def quotation_market(market_id, quotation_type, amount)
|
155
|
+
quotation(market_id, quotation_type, amount)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Get a limit quotation (with limit price)
|
159
|
+
#
|
160
|
+
# @param market_id [String] market identifier
|
161
|
+
# @param quotation_type [String] type of quotation
|
162
|
+
# @param amount [Float] amount for quotation
|
163
|
+
# @param limit [Float] limit price
|
164
|
+
# @return [Quotation] limit quotation details
|
165
|
+
def quotation_limit(market_id, quotation_type, amount, limit)
|
166
|
+
quotation(market_id, quotation_type, amount, limit: limit)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Get average prices report
|
170
|
+
#
|
171
|
+
# @param market_id [String] market identifier
|
172
|
+
# @param start_at [Time, nil] start time for report
|
173
|
+
# @param end_at [Time, nil] end time for report
|
174
|
+
# @return [Array<AveragePrice>] average price data points
|
175
|
+
# @example
|
176
|
+
# client = BudaApi::PublicClient.new
|
177
|
+
# start_time = Time.now - 86400 # 24 hours ago
|
178
|
+
# prices = client.average_prices_report("BTC-CLP", start_at: start_time)
|
179
|
+
def average_prices_report(market_id, start_at: nil, end_at: nil)
|
180
|
+
get_report(market_id, ReportType::AVERAGE_PRICES, start_at, end_at) do |report_data|
|
181
|
+
AveragePrice.new(report_data)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Get candlestick report
|
186
|
+
#
|
187
|
+
# @param market_id [String] market identifier
|
188
|
+
# @param start_at [Time, nil] start time for report
|
189
|
+
# @param end_at [Time, nil] end time for report
|
190
|
+
# @return [Array<Candlestick>] candlestick data points
|
191
|
+
# @example
|
192
|
+
# client = BudaApi::PublicClient.new
|
193
|
+
# start_time = Time.now - 86400 # 24 hours ago
|
194
|
+
# candles = client.candlestick_report("BTC-CLP", start_at: start_time)
|
195
|
+
def candlestick_report(market_id, start_at: nil, end_at: nil)
|
196
|
+
get_report(market_id, ReportType::CANDLESTICK, start_at, end_at) do |report_data|
|
197
|
+
Candlestick.new(report_data)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def get_report(market_id, report_type, start_at, end_at)
|
204
|
+
validate_required_params({
|
205
|
+
market_id: market_id,
|
206
|
+
report_type: report_type
|
207
|
+
}, [:market_id, :report_type])
|
208
|
+
|
209
|
+
validate_param_values({
|
210
|
+
market_id: market_id,
|
211
|
+
report_type: report_type
|
212
|
+
}, {
|
213
|
+
market_id: Market::ALL,
|
214
|
+
report_type: ReportType::ALL
|
215
|
+
})
|
216
|
+
|
217
|
+
params = normalize_params({
|
218
|
+
report_type: report_type,
|
219
|
+
from: start_at&.to_i,
|
220
|
+
to: end_at&.to_i
|
221
|
+
})
|
222
|
+
|
223
|
+
BudaApi::Logger.info("Fetching #{report_type} report for #{market_id}")
|
224
|
+
|
225
|
+
response = get("markets/#{market_id}/reports", params)
|
226
|
+
reports_data = response["reports"] || []
|
227
|
+
|
228
|
+
reports_data.map { |report_data| yield(report_data) }
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
data/lib/buda_api.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "buda_api/version"
|
4
|
+
require_relative "buda_api/client"
|
5
|
+
require_relative "buda_api/public_client"
|
6
|
+
require_relative "buda_api/authenticated_client"
|
7
|
+
require_relative "buda_api/models"
|
8
|
+
require_relative "buda_api/constants"
|
9
|
+
require_relative "buda_api/errors"
|
10
|
+
require_relative "buda_api/logger"
|
11
|
+
|
12
|
+
# BudaApi is the main module for the Buda.com API Ruby SDK
|
13
|
+
#
|
14
|
+
# This SDK provides comprehensive access to the Buda.com cryptocurrency exchange API
|
15
|
+
# with built-in error handling, debugging capabilities, and extensive examples.
|
16
|
+
#
|
17
|
+
# @example Basic usage with public API
|
18
|
+
# client = BudaApi::PublicClient.new
|
19
|
+
# markets = client.markets
|
20
|
+
# ticker = client.ticker("BTC-CLP")
|
21
|
+
#
|
22
|
+
# @example Authenticated API usage
|
23
|
+
# client = BudaApi::AuthenticatedClient.new(api_key: "your_key", api_secret: "your_secret")
|
24
|
+
# balance = client.balance("BTC")
|
25
|
+
# order = client.place_order("BTC-CLP", "ask", "limit", 1000000, 0.001)
|
26
|
+
#
|
27
|
+
module BudaApi
|
28
|
+
class Error < StandardError; end
|
29
|
+
|
30
|
+
# Configure the SDK with global settings
|
31
|
+
class Configuration
|
32
|
+
attr_accessor :base_url, :timeout, :retries, :debug_mode, :logger_level
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@base_url = "https://www.buda.com/api/v2/"
|
36
|
+
@timeout = 30
|
37
|
+
@retries = 3
|
38
|
+
@debug_mode = false
|
39
|
+
@logger_level = :info
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
attr_accessor :configuration
|
45
|
+
end
|
46
|
+
|
47
|
+
# Configure the SDK
|
48
|
+
#
|
49
|
+
# @yield [Configuration] configuration object
|
50
|
+
# @example
|
51
|
+
# BudaApi.configure do |config|
|
52
|
+
# config.debug_mode = true
|
53
|
+
# config.timeout = 60
|
54
|
+
# end
|
55
|
+
def self.configure
|
56
|
+
self.configuration ||= Configuration.new
|
57
|
+
yield(configuration)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Get current configuration
|
61
|
+
# @return [Configuration] current configuration
|
62
|
+
def self.configuration
|
63
|
+
@configuration ||= Configuration.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# Convenience method to create a public client
|
67
|
+
# @param options [Hash] client options
|
68
|
+
# @return [PublicClient] new public client instance
|
69
|
+
def self.public_client(options = {})
|
70
|
+
PublicClient.new(options)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convenience method to create an authenticated client
|
74
|
+
# @param api_key [String] API key
|
75
|
+
# @param api_secret [String] API secret
|
76
|
+
# @param options [Hash] additional client options
|
77
|
+
# @return [AuthenticatedClient] new authenticated client instance
|
78
|
+
def self.authenticated_client(api_key:, api_secret:, **options)
|
79
|
+
AuthenticatedClient.new(api_key: api_key, api_secret: api_secret, **options)
|
80
|
+
end
|
81
|
+
end
|