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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/.cursor/.gitignore +1 -0
  3. data/CHANGELOG.md +11 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +253 -0
  7. data/Rakefile +12 -0
  8. data/docs/AUTHENTICATION.md +49 -0
  9. data/docs/GETTING_STARTED.md +67 -0
  10. data/docs/RAILS_INTEGRATION.md +135 -0
  11. data/docs/REST_API_GUIDE.md +150 -0
  12. data/docs/STANDALONE_RUBY_GUIDE.md +73 -0
  13. data/docs/WEBSOCKET_GUIDE.md +160 -0
  14. data/exe/delta_exchange +4 -0
  15. data/lib/delta_exchange/auth.rb +12 -0
  16. data/lib/delta_exchange/client.rb +196 -0
  17. data/lib/delta_exchange/configuration.rb +40 -0
  18. data/lib/delta_exchange/constants.rb +72 -0
  19. data/lib/delta_exchange/contracts/order_contract.rb +24 -0
  20. data/lib/delta_exchange/contracts/position_contract.rb +21 -0
  21. data/lib/delta_exchange/contracts/wallet_transfer_contract.rb +16 -0
  22. data/lib/delta_exchange/core/base_model.rb +54 -0
  23. data/lib/delta_exchange/core/error_handler.rb +16 -0
  24. data/lib/delta_exchange/error.rb +37 -0
  25. data/lib/delta_exchange/helpers/attribute_helper.rb +22 -0
  26. data/lib/delta_exchange/helpers/validation_helper.rb +34 -0
  27. data/lib/delta_exchange/models/asset.rb +23 -0
  28. data/lib/delta_exchange/models/fee_tier.rb +19 -0
  29. data/lib/delta_exchange/models/fill.rb +20 -0
  30. data/lib/delta_exchange/models/funding_rate.rb +19 -0
  31. data/lib/delta_exchange/models/index.rb +23 -0
  32. data/lib/delta_exchange/models/open_interest.rb +19 -0
  33. data/lib/delta_exchange/models/order.rb +34 -0
  34. data/lib/delta_exchange/models/position.rb +43 -0
  35. data/lib/delta_exchange/models/product.rb +43 -0
  36. data/lib/delta_exchange/models/profile.rb +20 -0
  37. data/lib/delta_exchange/models/ticker.rb +26 -0
  38. data/lib/delta_exchange/models/trading_preferences.rb +27 -0
  39. data/lib/delta_exchange/models/wallet_balance.rb +23 -0
  40. data/lib/delta_exchange/models/wallet_transaction.rb +20 -0
  41. data/lib/delta_exchange/resources/account.rb +53 -0
  42. data/lib/delta_exchange/resources/assets.rb +11 -0
  43. data/lib/delta_exchange/resources/base.rb +37 -0
  44. data/lib/delta_exchange/resources/fills.rb +15 -0
  45. data/lib/delta_exchange/resources/heartbeat.rb +20 -0
  46. data/lib/delta_exchange/resources/indices.rb +11 -0
  47. data/lib/delta_exchange/resources/market_data.rb +56 -0
  48. data/lib/delta_exchange/resources/orders.rb +76 -0
  49. data/lib/delta_exchange/resources/positions.rb +47 -0
  50. data/lib/delta_exchange/resources/products.rb +39 -0
  51. data/lib/delta_exchange/resources/wallet.rb +45 -0
  52. data/lib/delta_exchange/version.rb +5 -0
  53. data/lib/delta_exchange/websocket/client.rb +55 -0
  54. data/lib/delta_exchange/websocket/connection.rb +114 -0
  55. data/lib/delta_exchange.rb +39 -0
  56. data/sig/delta_exchange.rbs +4 -0
  57. metadata +231 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-validation"
4
+
5
+ module DeltaExchange
6
+ module Contracts
7
+ class OrderContract < Dry::Validation::Contract
8
+ params do
9
+ required(:product_id).filled(:integer)
10
+ required(:size).filled(:integer)
11
+ required(:side).filled(:string, included_in?: DeltaExchange::Constants::Side::ALL)
12
+ required(:order_type).filled(:string, included_in?: DeltaExchange::Constants::OrderType::ALL)
13
+ optional(:limit_price).maybe(:string)
14
+ optional(:stop_price).maybe(:string)
15
+ optional(:client_order_id).maybe(:string)
16
+ optional(:time_in_force).maybe(:string, included_in?: DeltaExchange::Constants::TimeInForce::ALL)
17
+ end
18
+
19
+ rule(:limit_price) do
20
+ key.failure("is required for limit orders") if values[:order_type] == DeltaExchange::Constants::OrderType::LIMIT && values[:limit_price].nil?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-validation"
4
+
5
+ module DeltaExchange
6
+ module Contracts
7
+ class PositionContract < Dry::Validation::Contract
8
+ params do
9
+ required(:product_id).filled(:integer)
10
+ optional(:amount).maybe(:string)
11
+ optional(:type).maybe(:string, included_in?: %w[add remove])
12
+ optional(:leverage).maybe(:string)
13
+ optional(:auto_topup).maybe(:bool)
14
+ end
15
+
16
+ rule(:amount) do
17
+ key.failure("is required when type is present") if values[:type] && values[:amount].nil?
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-validation"
4
+
5
+ module DeltaExchange
6
+ module Contracts
7
+ class WalletTransferContract < Dry::Validation::Contract
8
+ params do
9
+ required(:asset_id).filled(:integer)
10
+ required(:amount).filled(:string)
11
+ required(:sub_account_id).filled(:integer)
12
+ required(:method).filled(:string, included_in?: %w[deposit withdraw])
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-validation"
4
+
5
+ module DeltaExchange
6
+ module Core
7
+ class BaseModel
8
+ include Helpers::AttributeHelper
9
+ include Helpers::ValidationHelper
10
+
11
+ attr_reader :raw_attributes, :errors
12
+
13
+ def initialize(attributes = {}, skip_validation: false)
14
+ @raw_attributes = normalize_keys(attributes)
15
+ @errors = {}
16
+ validate! unless skip_validation
17
+ assign_attributes
18
+ end
19
+
20
+ # Expose raw_attributes as attributes for backward compatibility or clarity.
21
+ def attributes
22
+ @raw_attributes
23
+ end
24
+
25
+ class << self
26
+ attr_reader :defined_attributes
27
+
28
+ def attributes(*args)
29
+ @defined_attributes ||= Set.new
30
+ @defined_attributes.merge(args.map(&:to_s))
31
+ args.each do |attr|
32
+ define_method(attr) { @raw_attributes[attr] }
33
+ end
34
+ @defined_attributes.to_a
35
+ end
36
+
37
+ def build_from_response(response)
38
+ payload = response.is_a?(Hash) && response.key?(:result) ? response[:result] : response
39
+ return payload.map { |r| new(r, skip_validation: true) } if payload.is_a?(Array)
40
+
41
+ new(payload, skip_validation: true)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def assign_attributes
48
+ self.class.defined_attributes&.each do |attr|
49
+ instance_variable_set("@#{attr}", @raw_attributes[attr])
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Core
5
+ class ErrorHandler
6
+ def self.handle(error)
7
+ case error
8
+ when Dry::Validation::Result
9
+ raise Error, "Validation failed: #{error.errors.to_h}"
10
+ else
11
+ raise Error, error.message
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ class Error < StandardError; end
5
+
6
+ class ApiError < Error
7
+ attr_reader :code, :response_body
8
+
9
+ def initialize(message = nil, code: nil, response_body: nil)
10
+ super(message)
11
+ @code = code
12
+ @response_body = response_body
13
+ end
14
+
15
+ def self.from_hash(hash, status: nil)
16
+ msg = hash[:error] || hash[:message] || "API error"
17
+ new(msg, code: status || hash[:code], response_body: hash)
18
+ end
19
+ end
20
+
21
+ class AuthenticationError < ApiError; end
22
+ class InvalidAuthenticationError < AuthenticationError; end
23
+
24
+ class RateLimitError < ApiError
25
+ attr_reader :retry_after_seconds
26
+
27
+ def initialize(message = nil, retry_after_seconds: nil, **)
28
+ super(message, **)
29
+ @retry_after_seconds = retry_after_seconds
30
+ end
31
+ end
32
+
33
+ class ValidationError < Error; end
34
+ class NotFoundError < ApiError; end
35
+ class InternalServerError < ApiError; end
36
+ class InputExceptionError < ApiError; end
37
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inflections"
4
+ require "active_support/core_ext/hash/keys"
5
+
6
+ module DeltaExchange
7
+ module Helpers
8
+ module AttributeHelper
9
+ def camelize_keys(hash)
10
+ hash.transform_keys { |key| key.to_s.camelize(:lower) }
11
+ end
12
+
13
+ def snake_case_keys(hash)
14
+ hash.transform_keys { |key| key.to_s.underscore.to_sym }
15
+ end
16
+
17
+ def normalize_keys(hash)
18
+ hash.transform_keys { |k| k.to_s.underscore }.with_indifferent_access
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Helpers
5
+ module ValidationHelper
6
+ def validate_params!(params, contract_class)
7
+ contract = contract_class.new
8
+ result = contract.call(params)
9
+
10
+ raise ValidationError, "Invalid parameters: #{result.errors.to_h}" unless result.success?
11
+
12
+ result.to_h
13
+ end
14
+
15
+ def validate!
16
+ contract_class = self.class.respond_to?(:validation_contract) ? self.class.validation_contract : nil
17
+ return unless contract_class
18
+
19
+ contract = contract_class.new
20
+ payload = instance_variable_get(:@raw_attributes) || instance_variable_get(:@attributes)
21
+ result = contract.call(payload)
22
+
23
+ return if result.success?
24
+
25
+ @errors = result.errors.to_h
26
+ raise ValidationError, "Invalid parameters: #{@errors.inspect}"
27
+ end
28
+
29
+ def valid?
30
+ @errors.nil? || @errors.empty?
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Asset < Core::BaseModel
6
+ attributes :id, :symbol, :precision, :is_deposit_enabled, :is_withdrawal_enabled, :network
7
+
8
+ class << self
9
+ def resource
10
+ @resource ||= DeltaExchange::Client.new.assets
11
+ end
12
+
13
+ def all
14
+ build_from_response(resource.all)
15
+ end
16
+
17
+ def find(id_or_symbol)
18
+ build_from_response(resource.find(id_or_symbol))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class FeeTier < Core::BaseModel
6
+ attributes :tier, :maker_fee, :taker_fee, :volume_30d, :min_volume
7
+
8
+ class << self
9
+ def resource
10
+ @resource ||= DeltaExchange::Client.new.account
11
+ end
12
+
13
+ def current
14
+ build_from_response(resource.fee_tiers)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Fill < Core::BaseModel
6
+ attributes :id, :order_id, :product_id, :symbol, :size, :price, :side,
7
+ :role, :fee, :fee_asset_id, :margin, :timestamp
8
+
9
+ class << self
10
+ def resource
11
+ @resource ||= DeltaExchange::Client.new.fills
12
+ end
13
+
14
+ def all(params = {})
15
+ build_from_response(resource.all(params))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class FundingRate < Core::BaseModel
6
+ attributes :product_id, :funding_rate, :funding_rate_daily, :funding_time
7
+
8
+ class << self
9
+ def resource
10
+ @resource ||= DeltaExchange::Client.new.market_data
11
+ end
12
+
13
+ def history(product_id, params = {})
14
+ build_from_response(resource.funding_rates(params.merge(product_id: product_id)))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Index < Core::BaseModel
6
+ attributes :id, :symbol, :description, :constituent_exchanges, :price_method, :is_composite
7
+
8
+ class << self
9
+ def resource
10
+ @resource ||= DeltaExchange::Client.new.indices
11
+ end
12
+
13
+ def all
14
+ build_from_response(resource.all)
15
+ end
16
+
17
+ def find(symbol)
18
+ build_from_response(resource.find(symbol))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class OpenInterest < Core::BaseModel
6
+ attributes :product_id, :open_interest, :time
7
+
8
+ class << self
9
+ def resource
10
+ @resource ||= DeltaExchange::Client.new.market_data
11
+ end
12
+
13
+ def history(product_id, params = {})
14
+ build_from_response(resource.open_interest(params.merge(product_id: product_id)))
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Order < Core::BaseModel
6
+ attributes :id, :client_order_id, :product_id, :side, :order_type, :limit_price,
7
+ :stop_price, :size, :filled_size, :unfilled_size, :average_fill_price,
8
+ :status, :created_at, :updated_at
9
+
10
+ class << self
11
+ def resource
12
+ # Lazily instantiate a client and reuse its Orders resource.
13
+ @resource ||= DeltaExchange::Client.new.orders
14
+ end
15
+
16
+ def all(params = {})
17
+ build_from_response(resource.all(params))
18
+ end
19
+
20
+ def find(id)
21
+ build_from_response(resource.find(id))
22
+ end
23
+
24
+ def create(payload)
25
+ build_from_response(resource.create(payload))
26
+ end
27
+ end
28
+
29
+ def cancel
30
+ self.class.resource.cancel(product_id: product_id, id: id)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Position < Core::BaseModel
6
+ attributes :product_id, :size, :side, :entry_price, :mark_price, :liquidation_price,
7
+ :margin, :unrealized_pnl, :realized_pnl, :commission, :auto_topup
8
+
9
+ class << self
10
+ def resource
11
+ @resource ||= DeltaExchange::Client.new.positions
12
+ end
13
+
14
+ def all(params = {})
15
+ build_from_response(resource.margined(params))
16
+ end
17
+
18
+ def find(product_id)
19
+ build_from_response(resource.find(product_id))
20
+ end
21
+
22
+ def close_all
23
+ resource.close_all
24
+ end
25
+ end
26
+
27
+ def adjust_margin(amount, type: "add")
28
+ self.class.resource.adjust_margin(
29
+ product_id: product_id,
30
+ amount: amount,
31
+ type: type
32
+ )
33
+ end
34
+
35
+ def set_auto_topup(enabled)
36
+ self.class.resource.set_auto_topup(
37
+ product_id: product_id,
38
+ auto_topup: enabled
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Product < Core::BaseModel
6
+ attributes :id, :symbol, :description, :contract_type, :contract_value, :lot_size, :tick_size,
7
+ :underlying_asset_symbol, :quoting_asset_symbol, :settlement_asset_symbol,
8
+ :state, :funding_method, :impact_size, :initial_margin, :maintenance_margin,
9
+ :strike_price, :expiration_time
10
+
11
+ # Delta documents per-contract sizing under product `contract_value` (and may add `lot_size`).
12
+ # Order `size` is in contracts; multiply by this for notional / linear PnL in the quoting currency.
13
+ def contract_lot_multiplier
14
+ raw = lot_size.presence || contract_value
15
+ return BigDecimal("0") if raw.blank?
16
+
17
+ BigDecimal(raw.to_s)
18
+ end
19
+
20
+ class << self
21
+ def resource
22
+ @resource ||= DeltaExchange::Client.new.products
23
+ end
24
+
25
+ def all(params = {})
26
+ build_from_response(resource.all(params))
27
+ end
28
+
29
+ def find(symbol)
30
+ build_from_response(resource.find(symbol))
31
+ end
32
+ end
33
+
34
+ def leverage
35
+ self.class.resource.leverage(id)
36
+ end
37
+
38
+ def set_leverage(new_leverage)
39
+ self.class.resource.set_leverage(id, { leverage: new_leverage })
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Profile < Core::BaseModel
6
+ attributes :id, :email, :first_name, :last_name, :country, :kyc_status,
7
+ :referral_code, :is_subaccount, :margin_mode, :api_trading_enabled
8
+
9
+ class << self
10
+ def resource
11
+ @resource ||= DeltaExchange::Client.new.account
12
+ end
13
+
14
+ def fetch
15
+ build_from_response(resource.profile)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class Ticker < Core::BaseModel
6
+ attributes :symbol, :contract_type, :mark_price, :spot_price, :strike_price,
7
+ :open, :high, :low, :close, :volume, :turnover, :turnover_usd,
8
+ :size, :quotes, :price_band, :greeks, :funding_rate, :oi, :oi_value,
9
+ :oi_value_usd, :ltp_change_24h, :mark_change_24h
10
+
11
+ class << self
12
+ def resource
13
+ @resource ||= DeltaExchange::Client.new.products
14
+ end
15
+
16
+ def all(params = {})
17
+ build_from_response(resource.tickers(params))
18
+ end
19
+
20
+ def find(symbol)
21
+ build_from_response(resource.ticker(symbol))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class TradingPreferences < Core::BaseModel
6
+ attributes :user_id, :vip_level, :volume_30d, :referral_discount_factor,
7
+ :default_auto_topup, :email_preferences, :notification_preferences,
8
+ :deto_balance, :deto_for_commission, :cancel_on_disconnect
9
+
10
+ class << self
11
+ def resource
12
+ @resource ||= DeltaExchange::Client.new.account
13
+ end
14
+
15
+ def fetch
16
+ build_from_response(resource.trading_preferences)
17
+ end
18
+ end
19
+
20
+ def update(cancel_on_disconnect: true)
21
+ self.class.resource.update_trading_preferences(
22
+ cancel_on_disconnect: cancel_on_disconnect
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class WalletBalance < Core::BaseModel
6
+ attributes :asset_id, :asset_symbol, :balance, :available_balance, :pending_withdrawal, :commission
7
+
8
+ class << self
9
+ def resource
10
+ @resource ||= DeltaExchange::Client.new.wallet
11
+ end
12
+
13
+ def all
14
+ build_from_response(resource.balances)
15
+ end
16
+
17
+ def find_by_asset(symbol)
18
+ all.find { |b| b.asset_symbol == symbol.to_s.upcase }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Models
5
+ class WalletTransaction < Core::BaseModel
6
+ attributes :id, :asset_id, :amount, :type, :status, :timestamp,
7
+ :tx_hash, :network, :address
8
+
9
+ class << self
10
+ def resource
11
+ @resource ||= DeltaExchange::Client.new.wallet
12
+ end
13
+
14
+ def all(params = {})
15
+ build_from_response(resource.transactions(params))
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Resources
5
+ class Account < Base
6
+ def profile
7
+ get("/v2/profile")
8
+ end
9
+
10
+ def trading_preferences
11
+ get("/v2/users/trading_preferences")
12
+ end
13
+
14
+ def update_trading_preferences(payload)
15
+ put("/v2/users/trading_preferences", payload)
16
+ end
17
+
18
+ def change_margin_mode(payload)
19
+ put("/v2/users/margin_mode", payload)
20
+ end
21
+
22
+ def subaccounts
23
+ get("/v2/sub_accounts")
24
+ end
25
+
26
+ def update_mmp(payload)
27
+ put("/v2/users/update_mmp", payload)
28
+ end
29
+
30
+ def reset_mmp(payload)
31
+ put("/v2/users/reset_mmp", payload)
32
+ end
33
+
34
+ def fee_tiers
35
+ get("/v2/users/fee_tiers")
36
+ end
37
+
38
+ def referrals
39
+ get("/v2/users/referrals")
40
+ end
41
+
42
+ # @deprecated Use {#trading_preferences} - Delta v2 uses +/v2/users/trading_preferences+.
43
+ def preferences
44
+ trading_preferences
45
+ end
46
+
47
+ # @deprecated Use {#update_trading_preferences}
48
+ def update_preferences(payload)
49
+ update_trading_preferences(payload)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeltaExchange
4
+ module Resources
5
+ class Assets < Base
6
+ def all
7
+ get("/v2/assets", {}, authenticate: false)
8
+ end
9
+ end
10
+ end
11
+ end