0.0
The project is in a healthy, maintained state
CoinDCX-specific gem with separate REST and Socket.io layers, modeled after layered exchange client architecture.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

coindcx-client

Production-grade Ruby SDK for the CoinDCX exchange. Full REST + real-time WebSocket coverage with built-in safety defaults for trading systems.

Ruby License: MIT

Why this gem?

Feature coindcx-client
All official REST namespaces ✓ public, spot, margin, futures, funding, transfers, user
Idempotency enforcement client_order_id required on all create paths
Per-endpoint rate limiting ✓ 60+ buckets with token-bucket algorithm
Circuit breaker ✓ fail-fast on repeated order failures
Exponential-backoff retries ✓ respects Retry-After headers
WebSocket with auto-reconnect ✓ subscription replay, heartbeat liveness
Structured error hierarchy ✓ 10+ classes, all with retryable flag
Typed models ✓ Order, Trade, Market, Position, Instrument, Balance, Candle
Validated keyword APIs ✓ required params enforced at call site

Quick start (5 minutes)

1. Add to Gemfile

gem 'coindcx-client', '~> 1.0'

2. Configure

require 'coindcx'

CoinDCX.configure do |config|
  config.api_key    = ENV.fetch('COINDCX_API_KEY')
  config.api_secret = ENV.fetch('COINDCX_API_SECRET')
  config.logger     = Logger.new($stdout)
end

3. Call a public endpoint

client = CoinDCX.client
client.public.market_data.list_tickers.first

4. Place an order with idempotency

client.spot.orders.create(
  side:             'buy',
  order_type:       'limit_order',
  market:           'SNTBTC',
  price_per_unit:   '0.03244',
  total_quantity:   400,
  client_order_id:  CoinDCX.generate_client_order_id  # always persist this first
)

5. Subscribe to live prices

ws      = client.ws
channel = CoinDCX::WS::PublicChannels.price_stats(pair: 'B-BTC_USDT')

ws.connect
ws.subscribe_public(channel_name: channel, event_name: 'price-change') do |payload|
  puts "#{payload['s']}: #{payload['p']}"
end

sleep  # keep the process alive

Full API reference

Public market data

pub = client.public.market_data

pub.list_tickers                  # → [Market, ...]
pub.list_markets                  # → ["SNTBTC", ...]
pub.list_market_details           # → [Market, ...]
pub.list_trades(pair: 'B-BTC_USDT', limit: 50)   # → [Trade, ...]
pub.fetch_order_book(pair: 'B-BTC_USDT')         # → { "bids" => ..., "asks" => ... }
pub.list_candles(
  pair:       'B-BTC_USDT',
  interval:   '1h',          # 1m 5m 15m 30m 1h 2h 4h 6h 8h 1d 3d 1w 1M
  start_time: 1_700_000_000_000,
  end_time:   1_700_100_000_000,
  limit:      500
)                                 # → [Candle, ...]

User account

user = client.user.accounts

user.list_balances   # → [Balance, ...]
user.fetch_info      # → { "coindcx_id" => ..., "email" => ... }

Spot orders

All methods require client_order_id for create operations. The transport rejects calls without it.

spot = client.spot.orders

# Create
spot.create(
  side: 'buy', order_type: 'limit_order', market: 'SNTBTC',
  price_per_unit: '0.03244', total_quantity: 400,
  client_order_id: CoinDCX.generate_client_order_id
)                                               # → Order

# Create multiple (INR markets only; exchange restriction)
spot.create_many(orders: [...])                 # → [Order, ...]

# Query
spot.fetch_status(id: 'order-id')              # → Order
spot.fetch_status(client_order_id: 'my-ref')   # → Order
spot.fetch_statuses(ids: ['id-1', 'id-2'])     # → [Order, ...]
spot.fetch_statuses(client_order_ids: [...])   # → [Order, ...]

# Active orders — market: is required by the exchange
spot.list_active(market: 'SNTBTC')             # → [Order, ...]
spot.list_active(market: 'SNTBTC', side: 'buy')
spot.count_active(market: 'SNTBTC')            # → { "count" => 3 }

# Trade history — all filters optional
spot.list_trade_history                         # → [Trade, ...]
spot.list_trade_history(
  limit:          200,
  sort:           'desc',     # 'asc' | 'desc'
  symbol:         'SNTBTC',
  from_timestamp: 1_700_000_000_000,
  to_timestamp:   1_700_100_000_000
)

# Cancel
spot.cancel(id: 'order-id')                    # → Order
spot.cancel(client_order_id: 'my-ref')         # → Order
spot.cancel_many(ids: ['id-1', 'id-2'])        # → [Order, ...]
spot.cancel_all(market: 'SNTBTC')              # cancel all open; market: required
spot.cancel_all(market: 'SNTBTC', side: 'buy')

# Edit price (limit orders only)
spot.edit_price(id: 'order-id', price_per_unit: '0.035')   # → Order

Margin orders

margin = client.margin.orders

margin.create(
  side: 'buy', order_type: 'limit_order', market: 'SNTBTC',
  quantity: 400, price_per_unit: '0.03244',
  client_order_id: CoinDCX.generate_client_order_id
)                                              # → Order

margin.list                                   # → [Order, ...]
margin.fetch(id: 'order-id')                 # → Order
margin.cancel(id: 'order-id')
margin.cancel(client_order_id: 'my-ref')
margin.exit_order(id: 'order-id')
margin.edit_target(id: 'order-id', target_price: '0.04')
margin.edit_stop_loss(id: 'order-id', sl_price: '0.03')
margin.edit_trailing_stop_loss(id: 'order-id', sl_price: '0.03')
margin.edit_target_order_price(id: 'order-id', price_per_unit: '0.039')
margin.add_margin(id: 'order-id', amount: 100)
margin.remove_margin(id: 'order-id', amount: 50)

Futures

Market data (public)

fmd = client.futures.market_data

fmd.list_active_instruments(margin_currency_short_names: ['USDT'])  # → [Hash, ...]
fmd.fetch_instrument(pair: 'B-BTC_USDT', margin_currency_short_name: 'USDT')  # → Instrument
fmd.list_trades(pair: 'B-BTC_USDT')           # → [Trade, ...]
fmd.fetch_order_book(instrument: 'BTCUSDT', depth: 20)  # depth: 10 | 20 | 50
fmd.list_candlesticks(pair: 'B-BTC_USDT', from: ts, to: ts, resolution: '1')
fmd.current_prices                             # → Hash
fmd.stats(pair: 'B-BTC_USDT')                # → Hash
fmd.conversions                               # → Hash

Orders

fo = client.futures.orders

fo.list(status: 'open', margin_currency_short_name: ['USDT'])  # → [Order, ...]
fo.list_trades(page: 1, size: 50)             # → [Trade, ...]
fo.create(order: {
  side: 'buy', pair: 'B-BTC_USDT', size: 1,
  order_type: 'limit_order', price: '50000',
  client_order_id: CoinDCX.generate_client_order_id
})                                             # → Order
fo.cancel(id: 'order-id')
fo.cancel(client_order_id: 'my-ref')
fo.edit(id: 'order-id', price: '51000')       # → Order

Positions

fp = client.futures.positions

fp.list                                        # → [Position, ...]
fp.update_leverage(id: 'pos-id', leverage: 10)
fp.add_margin(id: 'pos-id', amount: 100)
fp.remove_margin(id: 'pos-id', amount: 50)
fp.cancel_all_open_orders(id: 'pos-id')
fp.cancel_all_open_orders_for_position(id: 'pos-id')
fp.exit_position(id: 'pos-id')
fp.create_take_profit_stop_loss(id: 'pos-id', target_price: '55000', sl_price: '48000')
fp.list_transactions                           # → [Hash, ...]
fp.fetch_cross_margin_details                  # → Hash
fp.update_margin_type(id: 'pos-id', margin_type: 'isolated')

Wallets

fw = client.futures.wallets

fw.transfer(type: 'in', amount: 100, currency: 'USDT')
fw.fetch_details                              # → Hash
fw.list_transactions(page: 1, size: 50)      # → [Hash, ...]

Lending / Funding

funding = client.funding.orders

funding.list                                         # → [Hash, ...]
funding.lend(currency_short_name: 'USDT', amount: '100', duration: 7)
funding.settle(id: 'funding-order-id')

Wallet transfers

wallets = client.transfers.wallets

# Move between spot and futures wallets
wallets.transfer(
  source_wallet_type:      'spot',
  destination_wallet_type: 'futures',
  currency_short_name:     'USDT',
  amount:                  100
)

# Sub-account transfer (API keys created after 2024-08-12 only)
wallets.sub_account_transfer(
  from_account_id:    'acct-a',
  to_account_id:      'acct-b',
  currency_short_name: 'USDT',
  amount:              50
)

WebSocket

ws = client.ws
ws.connect

# Public streams
channel = CoinDCX::WS::PublicChannels.price_stats(pair: 'B-BTC_USDT')
ws.subscribe_public(channel_name: channel, event_name: CoinDCX::WS::PublicChannels::PRICE_CHANGE_EVENT) do |payload|
  puts payload
end

# Order book (depth: 10, 20, or 50)
ob = CoinDCX::WS::PublicChannels.order_book('B-BTC_USDT', depth: 20)
ws.subscribe_public(channel_name: ob, event_name: CoinDCX::WS::PublicChannels::DEPTH_SNAPSHOT_EVENT) { |snap| ... }
ws.subscribe_public(channel_name: ob, event_name: CoinDCX::WS::PublicChannels::DEPTH_UPDATE_EVENT)  { |delta| ... }

# Candlesticks
candle_ch = CoinDCX::WS::PublicChannels.candlestick('B-BTC_USDT', '1m')
ws.subscribe_public(channel_name: candle_ch, event_name: CoinDCX::WS::PublicChannels::CANDLESTICK_EVENT) { |c| ... }

# New trades
trades_ch = CoinDCX::WS::PublicChannels.new_trade('B-BTC_USDT')
ws.subscribe_public(channel_name: trades_ch, event_name: CoinDCX::WS::PublicChannels::NEW_TRADE_EVENT) { |t| ... }

# Private streams (requires api_key + api_secret)
ws.subscribe_private(event_name: CoinDCX::WS::PrivateChannels::ORDER_UPDATE_EVENT)   { |e| ... }
ws.subscribe_private(event_name: CoinDCX::WS::PrivateChannels::TRADE_UPDATE_EVENT)   { |e| ... }
ws.subscribe_private(event_name: CoinDCX::WS::PrivateChannels::BALANCE_UPDATE_EVENT) { |e| ... }

# Futures private streams
ws.subscribe_private(event_name: CoinDCX::WS::PrivateChannels::DF_ORDER_UPDATE_EVENT)    { |e| ... }
ws.subscribe_private(event_name: CoinDCX::WS::PrivateChannels::DF_POSITION_UPDATE_EVENT) { |e| ... }

# Futures public streams
fi_ch = CoinDCX::WS::PublicChannels.futures_candlestick('BTCUSDT', '1m')
ws.subscribe_public(channel_name: fi_ch, event_name: CoinDCX::WS::PublicChannels::CANDLESTICK_EVENT) { |c| ... }

# Liveness check
ws.alive?          # → true / false
ws.disconnect

WebSocket guarantees:

  • Auto-reconnect with exponential backoff (configurable attempts + interval)
  • Private auth re-sent on every reconnect
  • All subscriptions replayed after reconnect
  • Heartbeat liveness timeout raises SocketHeartbeatTimeoutError

Error handling

Every error class exposes .status, .category, .code, .request_context, .retryable.

begin
  client.spot.orders.create(...)
rescue CoinDCX::Errors::ValidationError => e
  # client_order_id missing, bad side/market/qty
rescue CoinDCX::Errors::AuthError => e
  # 401 — bad key/secret
rescue CoinDCX::Errors::RetryableRateLimitError => e
  # 429 with Retry-After — SDK already backed off; this means budget exhausted
rescue CoinDCX::Errors::RateLimitError => e
  # 429 without Retry-After
rescue CoinDCX::Errors::CircuitOpenError => e
  # repeated create failures — circuit is open; pause and investigate
rescue CoinDCX::Errors::RemoteValidationError => e
  # 400/422 — exchange rejected payload
rescue CoinDCX::Errors::UpstreamServerError => e
  # 5xx from exchange
rescue CoinDCX::Errors::TransportError => e
  # network-level failure
end

Socket errors:

rescue CoinDCX::Errors::SocketConnectionError
rescue CoinDCX::Errors::SocketAuthenticationError
rescue CoinDCX::Errors::SocketHeartbeatTimeoutError

Configuration reference

CoinDCX.configure do |c|
  # Credentials (required for private endpoints)
  c.api_key    = ENV.fetch('COINDCX_API_KEY')
  c.api_secret = ENV.fetch('COINDCX_API_SECRET')

  # HTTP
  c.open_timeout       = 5     # seconds
  c.read_timeout       = 30    # seconds

  # Retry
  c.max_retries          = 2
  c.retry_base_interval  = 0.25   # seconds (exponential backoff base)

  # Retry budgets per request class
  c.market_data_retry_budget     = 2  # public GETs
  c.private_read_retry_budget    = 1  # authenticated reads
  c.idempotent_order_retry_budget = 1  # create with client_order_id

  # Circuit breaker (order create paths)
  c.circuit_breaker_threshold  = 3    # failures before opening
  c.circuit_breaker_cooldown   = 30.0 # seconds

  # WebSocket
  c.socket_base_url           = 'wss://stream.coindcx.com'
  c.socket_reconnect_attempts = 5
  c.socket_reconnect_interval = 1.0   # seconds
  c.socket_heartbeat_interval = 10.0  # seconds
  c.socket_liveness_timeout   = 60.0  # seconds (raises SocketHeartbeatTimeoutError)

  # Logging
  c.logger = Logger.new($stdout)
end

Trading safety rules

  1. Persist client_order_id before API submission. On timeout, reconcile using your persisted key via fetch_status.
  2. Never reuse a client_order_id for a different order. Generate with CoinDCX.generate_client_order_id.
  3. WebSocket delivery is at-least-once across reconnects. Dedupe order/trade events in your app layer.
  4. After reconnects or restarts, reconcile missed events via REST snapshots before resuming.
  5. Map each error class to explicit policy — retry, abort, or operator alert. Avoid string-matching error bodies.

Documentation

Requirements

  • Ruby >= 3.2
  • faraday ~> 2.14
  • socket.io-client-simple ~> 1.2

License

MIT