delta_exchange 0.1.2
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/.cursor/.gitignore +1 -0
- data/CHANGELOG.md +11 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +253 -0
- data/Rakefile +12 -0
- data/docs/AUTHENTICATION.md +49 -0
- data/docs/GETTING_STARTED.md +67 -0
- data/docs/RAILS_INTEGRATION.md +135 -0
- data/docs/REST_API_GUIDE.md +150 -0
- data/docs/STANDALONE_RUBY_GUIDE.md +73 -0
- data/docs/WEBSOCKET_GUIDE.md +160 -0
- data/exe/delta_exchange +4 -0
- data/lib/delta_exchange/auth.rb +12 -0
- data/lib/delta_exchange/client.rb +196 -0
- data/lib/delta_exchange/configuration.rb +40 -0
- data/lib/delta_exchange/constants.rb +72 -0
- data/lib/delta_exchange/contracts/order_contract.rb +24 -0
- data/lib/delta_exchange/contracts/position_contract.rb +21 -0
- data/lib/delta_exchange/contracts/wallet_transfer_contract.rb +16 -0
- data/lib/delta_exchange/core/base_model.rb +54 -0
- data/lib/delta_exchange/core/error_handler.rb +16 -0
- data/lib/delta_exchange/error.rb +37 -0
- data/lib/delta_exchange/helpers/attribute_helper.rb +22 -0
- data/lib/delta_exchange/helpers/validation_helper.rb +34 -0
- data/lib/delta_exchange/models/asset.rb +23 -0
- data/lib/delta_exchange/models/fee_tier.rb +19 -0
- data/lib/delta_exchange/models/fill.rb +20 -0
- data/lib/delta_exchange/models/funding_rate.rb +19 -0
- data/lib/delta_exchange/models/index.rb +23 -0
- data/lib/delta_exchange/models/open_interest.rb +19 -0
- data/lib/delta_exchange/models/order.rb +34 -0
- data/lib/delta_exchange/models/position.rb +43 -0
- data/lib/delta_exchange/models/product.rb +43 -0
- data/lib/delta_exchange/models/profile.rb +20 -0
- data/lib/delta_exchange/models/ticker.rb +26 -0
- data/lib/delta_exchange/models/trading_preferences.rb +27 -0
- data/lib/delta_exchange/models/wallet_balance.rb +23 -0
- data/lib/delta_exchange/models/wallet_transaction.rb +20 -0
- data/lib/delta_exchange/resources/account.rb +53 -0
- data/lib/delta_exchange/resources/assets.rb +11 -0
- data/lib/delta_exchange/resources/base.rb +37 -0
- data/lib/delta_exchange/resources/fills.rb +15 -0
- data/lib/delta_exchange/resources/heartbeat.rb +20 -0
- data/lib/delta_exchange/resources/indices.rb +11 -0
- data/lib/delta_exchange/resources/market_data.rb +56 -0
- data/lib/delta_exchange/resources/orders.rb +76 -0
- data/lib/delta_exchange/resources/positions.rb +47 -0
- data/lib/delta_exchange/resources/products.rb +39 -0
- data/lib/delta_exchange/resources/wallet.rb +45 -0
- data/lib/delta_exchange/version.rb +5 -0
- data/lib/delta_exchange/websocket/client.rb +55 -0
- data/lib/delta_exchange/websocket/connection.rb +114 -0
- data/lib/delta_exchange.rb +39 -0
- data/sig/delta_exchange.rbs +4 -0
- metadata +231 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeltaExchange
|
|
4
|
+
module Resources
|
|
5
|
+
class Base
|
|
6
|
+
attr_reader :client
|
|
7
|
+
|
|
8
|
+
def initialize(client = nil)
|
|
9
|
+
raise ArgumentError, "client must be provided" unless client
|
|
10
|
+
|
|
11
|
+
@client = client
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
protected
|
|
15
|
+
|
|
16
|
+
def get(path, params = {}, authenticate: true)
|
|
17
|
+
client.get(path, params, authenticate: authenticate)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def post(path, payload = {}, authenticate: true)
|
|
21
|
+
client.post(path, payload, authenticate: authenticate)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def put(path, payload = {}, authenticate: true)
|
|
25
|
+
client.put(path, payload, authenticate: authenticate)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def patch(path, payload = {}, authenticate: true)
|
|
29
|
+
client.patch(path, payload, authenticate: authenticate)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def delete(path, payload = {}, params = {}, authenticate: true)
|
|
33
|
+
client.delete(path, payload, params, authenticate: authenticate)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeltaExchange
|
|
4
|
+
module Resources
|
|
5
|
+
class Fills < Base
|
|
6
|
+
def all(params = {})
|
|
7
|
+
get("/v2/fills", params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def history_csv(params = {})
|
|
11
|
+
get("/v2/fills/history/download/csv", params)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeltaExchange
|
|
4
|
+
module Resources
|
|
5
|
+
# Session keepalive endpoints; confirm paths against your Delta environment (not in public REST slate list).
|
|
6
|
+
class Heartbeat < Base
|
|
7
|
+
def create(payload)
|
|
8
|
+
post("/v2/heartbeats", payload)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ack(payload)
|
|
12
|
+
put("/v2/heartbeats/ack", payload)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def all(params = {})
|
|
16
|
+
get("/v2/heartbeats", params)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module DeltaExchange
|
|
6
|
+
module Resources
|
|
7
|
+
class MarketData < Base
|
|
8
|
+
def l2_orderbook(symbol, params = {})
|
|
9
|
+
sym = CGI.escape(symbol.to_s)
|
|
10
|
+
get("/v2/l2orderbook/#{sym}", params, authenticate: false)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def trades(symbol, params = {})
|
|
14
|
+
sym = CGI.escape(symbol.to_s)
|
|
15
|
+
get("/v2/trades/#{sym}", params, authenticate: false)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def candles(params = {})
|
|
19
|
+
get("/v2/history/candles", params, authenticate: false)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def sparklines(params = {})
|
|
23
|
+
get("/v2/history/sparklines", params, authenticate: false)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stats(params = {})
|
|
27
|
+
get("/v2/stats", params, authenticate: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def mark_price(symbol)
|
|
31
|
+
sym = CGI.escape(symbol.to_s)
|
|
32
|
+
get("/v2/mark_price/#{sym}", {}, authenticate: false)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def insurance_fund
|
|
36
|
+
get("/v2/insurance_fund", {}, authenticate: false)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def option_greeks(params = {})
|
|
40
|
+
get("/v2/option_greeks", params, authenticate: false)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def funding_rates(params = {})
|
|
44
|
+
get("/v2/history/funding_rates", params, authenticate: false)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def open_interest(params = {})
|
|
48
|
+
get("/v2/history/open_interest", params, authenticate: false)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def volume_stats(params = {})
|
|
52
|
+
get("/v2/stats/volume", params, authenticate: false)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module DeltaExchange
|
|
6
|
+
module Resources
|
|
7
|
+
class Orders < Base
|
|
8
|
+
def all(params = {})
|
|
9
|
+
get("/v2/orders", params)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def find(id)
|
|
13
|
+
get("/v2/orders/#{id}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find_by_client_order_id(client_order_id)
|
|
17
|
+
oid = CGI.escape(client_order_id.to_s)
|
|
18
|
+
get("/v2/orders/client_order_id/#{oid}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def history(params = {})
|
|
22
|
+
get("/v2/orders/history", params)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create(payload)
|
|
26
|
+
validate_order!(payload)
|
|
27
|
+
post("/v2/orders", payload)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_bracket(payload)
|
|
31
|
+
# Bracket orders have similar but slightly different fields
|
|
32
|
+
post("/v2/orders/bracket", payload)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create_batch(payload)
|
|
36
|
+
post("/v2/orders/batch", payload)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def update(payload)
|
|
40
|
+
validate_order!(payload)
|
|
41
|
+
put("/v2/orders", payload)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def validate_order!(payload)
|
|
47
|
+
result = Contracts::OrderContract.new.call(payload)
|
|
48
|
+
return if result.success?
|
|
49
|
+
|
|
50
|
+
raise ValidationError, "Invalid order parameters: #{result.errors.to_h}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param payload [Hash] payload containing product_id and id (or client_order_id)
|
|
54
|
+
# @example client.orders.cancel(product_id: 1, id: 123)
|
|
55
|
+
def cancel(payload)
|
|
56
|
+
delete("/v2/orders", payload)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def cancel_all(params = {})
|
|
60
|
+
delete("/v2/orders/all", {}, params)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cancel_batch(payload)
|
|
64
|
+
delete("/v2/orders/batch", payload)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def cancel_after(payload)
|
|
68
|
+
post("/v2/orders/cancel_after", payload)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def set_leverage(payload)
|
|
72
|
+
post("/v2/orders/leverage", payload)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeltaExchange
|
|
4
|
+
module Resources
|
|
5
|
+
class Positions < Base
|
|
6
|
+
def all(params = {})
|
|
7
|
+
get("/v2/positions", params)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def margined(params = {})
|
|
11
|
+
get("/v2/positions/margined", params)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def find(product_id)
|
|
15
|
+
get("/v2/positions/#{product_id}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def adjust_margin(payload)
|
|
19
|
+
validate_position!(payload)
|
|
20
|
+
post("/v2/positions/change_margin", payload)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def change_leverage(payload)
|
|
24
|
+
validate_position!(payload)
|
|
25
|
+
post("/v2/positions/change_leverage", payload)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def validate_position!(payload)
|
|
31
|
+
result = Contracts::PositionContract.new.call(payload)
|
|
32
|
+
return if result.success?
|
|
33
|
+
|
|
34
|
+
raise ValidationError, "Invalid position parameters: #{result.errors.to_h}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def auto_topup(payload)
|
|
38
|
+
put("/v2/positions/auto_topup", payload)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def close_all(payload = {})
|
|
42
|
+
post("/v2/positions/close_all", payload)
|
|
43
|
+
end
|
|
44
|
+
public :auto_topup, :close_all
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module DeltaExchange
|
|
6
|
+
module Resources
|
|
7
|
+
class Products < Base
|
|
8
|
+
def all(params = {})
|
|
9
|
+
get("/v2/products", params, authenticate: false)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def find(symbol)
|
|
13
|
+
sym = CGI.escape(symbol.to_s)
|
|
14
|
+
get("/v2/products/#{sym}", {}, authenticate: false)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def tickers(params = {})
|
|
18
|
+
get("/v2/tickers", params, authenticate: false)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ticker(symbol)
|
|
22
|
+
sym = CGI.escape(symbol.to_s)
|
|
23
|
+
get("/v2/tickers/#{sym}", {}, authenticate: false)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def settlement_prices(params = {})
|
|
27
|
+
get("/v2/settlement_prices", params, authenticate: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def leverage(product_id)
|
|
31
|
+
get("/v2/products/#{product_id}/orders/leverage")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def set_leverage(product_id, payload)
|
|
35
|
+
post("/v2/products/#{product_id}/orders/leverage", payload)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeltaExchange
|
|
4
|
+
module Resources
|
|
5
|
+
class Wallet < Base
|
|
6
|
+
def balances
|
|
7
|
+
get("/v2/wallet/balances")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def transactions(params = {})
|
|
11
|
+
get("/v2/wallet/transactions", params)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def transactions_download(params = {})
|
|
15
|
+
get("/v2/wallet/transactions/download", params)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def subaccount_transfer_history(params = {})
|
|
19
|
+
get("/v2/wallets/sub_accounts_transfer_history", params)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def subaccount_transfer(payload)
|
|
23
|
+
validate_transfer!(payload)
|
|
24
|
+
post("/v2/wallets/sub_account_balance_transfer", payload)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def validate_transfer!(payload)
|
|
30
|
+
result = Contracts::WalletTransferContract.new.call(payload)
|
|
31
|
+
return if result.success?
|
|
32
|
+
|
|
33
|
+
raise ValidationError, "Invalid transfer parameters: #{result.errors.to_h}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def withdrawals(params = {})
|
|
37
|
+
get("/v2/wallet/withdrawals", params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def deposits(params = {})
|
|
41
|
+
get("/v2/wallet/deposits", params)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DeltaExchange
|
|
4
|
+
module Websocket
|
|
5
|
+
class Client
|
|
6
|
+
def initialize(api_key: nil, api_secret: nil, testnet: nil)
|
|
7
|
+
@api_key = api_key || DeltaExchange.configuration.api_key
|
|
8
|
+
@api_secret = api_secret || DeltaExchange.configuration.api_secret
|
|
9
|
+
use_testnet = testnet.nil? ? DeltaExchange.configuration.testnet : testnet
|
|
10
|
+
@url = use_testnet ? Constants::Urls::WEBSOCKET_TESTNET : Constants::Urls::WEBSOCKET_PRODUCTION
|
|
11
|
+
@callbacks = Hash.new { |h, k| h[k] = [] }
|
|
12
|
+
|
|
13
|
+
@connection = Connection.new(@url, api_key: @api_key, api_secret: @api_secret)
|
|
14
|
+
@connection.on_open = ->(event) { emit(:open, event) }
|
|
15
|
+
@connection.on_message = ->(msg) { emit(:message, msg) }
|
|
16
|
+
@connection.on_close = ->(event) { emit(:close, event) }
|
|
17
|
+
@connection.on_error = ->(event) { emit(:error, event) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def on(event, &block)
|
|
21
|
+
@callbacks[event] << block
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def connect!
|
|
26
|
+
@connection.start
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def subscribe(channels)
|
|
31
|
+
@connection.send_json({
|
|
32
|
+
type: "subscribe",
|
|
33
|
+
payload: { channels: channels }
|
|
34
|
+
})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def unsubscribe(channels)
|
|
38
|
+
@connection.send_json({
|
|
39
|
+
type: "unsubscribe",
|
|
40
|
+
payload: { channels: channels }
|
|
41
|
+
})
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def close
|
|
45
|
+
@connection.stop
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def emit(event, data)
|
|
51
|
+
@callbacks[event].each { |cb| cb.call(data) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faye/websocket"
|
|
4
|
+
require "eventmachine"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module DeltaExchange
|
|
8
|
+
module Websocket
|
|
9
|
+
class Connection
|
|
10
|
+
RECONNECT_DELAY = 5
|
|
11
|
+
attr_accessor :on_open, :on_message, :on_close, :on_error
|
|
12
|
+
|
|
13
|
+
def initialize(url, api_key: nil, api_secret: nil)
|
|
14
|
+
@url = url
|
|
15
|
+
@api_key = api_key
|
|
16
|
+
@api_secret = api_secret
|
|
17
|
+
@ws = nil
|
|
18
|
+
@stop = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def start
|
|
22
|
+
@thr = Thread.new { loop_run }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def stop
|
|
26
|
+
@stop = true
|
|
27
|
+
@ws&.close
|
|
28
|
+
EM.stop if EM.reactor_running?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def send_json(data)
|
|
32
|
+
@ws&.send(data.to_json)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def authenticate!
|
|
36
|
+
timestamp = Time.now.utc.to_i
|
|
37
|
+
path = "/v2/websocket"
|
|
38
|
+
method = "GET"
|
|
39
|
+
signature = Auth.sign(method, timestamp.to_s, path, "", "", @api_secret)
|
|
40
|
+
|
|
41
|
+
send_json({
|
|
42
|
+
type: "auth",
|
|
43
|
+
api_key: @api_key,
|
|
44
|
+
timestamp: timestamp,
|
|
45
|
+
signature: signature
|
|
46
|
+
})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def loop_run
|
|
52
|
+
until @stop
|
|
53
|
+
begin
|
|
54
|
+
if EM.reactor_running?
|
|
55
|
+
setup_ws
|
|
56
|
+
wait_for_connection_close
|
|
57
|
+
else
|
|
58
|
+
EM.run { setup_ws }
|
|
59
|
+
end
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
DeltaExchange.logger.error("[DeltaExchange::WS] Loop Error: #{e.message}")
|
|
62
|
+
end
|
|
63
|
+
handle_reconnect_delay
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def wait_for_connection_close
|
|
68
|
+
# Instead of a tight loop, we sleep for longer intervals while connected
|
|
69
|
+
# or we could use a ConditionVariable if we wanted to be more reactive.
|
|
70
|
+
# But even a longer sleep is better than the original "busy-wait".
|
|
71
|
+
# The WebSocket's on :close will resume the loop if EM stops.
|
|
72
|
+
sleep 1 while @ws&.ready_state == 1 && !@stop
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def handle_reconnect_delay
|
|
76
|
+
return if @stop
|
|
77
|
+
|
|
78
|
+
delay = DeltaExchange.configuration.websocket_reconnect_delay
|
|
79
|
+
sleep delay
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def setup_ws
|
|
83
|
+
headers = { "User-Agent" => DeltaExchange.configuration.user_agent }
|
|
84
|
+
@ws = Faye::WebSocket::Client.new(@url, nil, { headers: headers })
|
|
85
|
+
|
|
86
|
+
@ws.on :open do |event|
|
|
87
|
+
DeltaExchange.logger.info("[DeltaExchange::WS] Connected")
|
|
88
|
+
authenticate! if @api_key && @api_secret
|
|
89
|
+
@on_open&.call(event)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
@ws.on :message do |event|
|
|
93
|
+
data = begin
|
|
94
|
+
JSON.parse(event.data)
|
|
95
|
+
rescue StandardError
|
|
96
|
+
event.data
|
|
97
|
+
end
|
|
98
|
+
@on_message&.call(data)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@ws.on :close do |event|
|
|
102
|
+
DeltaExchange.logger.warn("[DeltaExchange::WS] Closed: #{event.code} #{event.reason}")
|
|
103
|
+
@on_close&.call(event)
|
|
104
|
+
EM.stop unless EM.reactor_running? # Only stop if we started it
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@ws.on :error do |event|
|
|
108
|
+
DeltaExchange.logger.error("[DeltaExchange::WS] Error: #{event.message}")
|
|
109
|
+
@on_error&.call(event)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
require "active_support/core_ext/hash/indifferent_access"
|
|
5
|
+
require "active_support/core_ext/object/blank"
|
|
6
|
+
require "logger"
|
|
7
|
+
|
|
8
|
+
module DeltaExchange
|
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
|
10
|
+
loader.setup
|
|
11
|
+
|
|
12
|
+
require_relative "delta_exchange/error"
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
attr_writer :configuration, :logger
|
|
16
|
+
|
|
17
|
+
def configuration
|
|
18
|
+
@configuration ||= Configuration.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def configure
|
|
22
|
+
self.configuration ||= Configuration.new
|
|
23
|
+
yield(configuration)
|
|
24
|
+
self.logger ||= Logger.new($stdout, level: Logger::INFO)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def logger
|
|
28
|
+
@logger ||= Logger.new($stdout, level: Logger::INFO)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ensure_configuration!
|
|
32
|
+
self.configuration ||= Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reset!
|
|
36
|
+
@configuration = Configuration.new
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|