Current section
Files
Jump to
Current section
Files
lib/ccxt/error.ex
defmodule CCXT.Error do
@moduledoc """
Unified error types for exchange operations.
All exchange errors are normalized to this struct, providing consistent
error handling across different exchanges.
## Error Types
- `:rate_limited` - Too many requests, retry after `retry_after` ms
- `:insufficient_balance` - Not enough funds for the operation
- `:invalid_credentials` - API key/secret rejected by exchange
- `:order_not_found` - Order ID does not exist
- `:invalid_order` - Order parameters are invalid
- `:market_closed` - Market is not currently trading
- `:network_error` - Connection or timeout issue
- `:access_restricted` - Geographic/IP block or access restriction (HTML response)
- `:not_supported` - Method not supported by this exchange class (CCXT inheritance quirk)
- `:circuit_open` - Circuit breaker tripped due to consecutive failures
- `:exchange_error` - Generic exchange error (see `code` and `message`)
## Example
case CCXT.Bybit.create_order(creds, "BTC/USDT", :limit, :buy, 0.001, 50000) do
{:ok, order} -> handle_order(order)
{:error, %CCXT.Error{type: :insufficient_balance}} -> notify_low_balance()
{:error, %CCXT.Error{type: :rate_limited, retry_after: ms}} -> Process.sleep(ms)
{:error, %CCXT.Error{} = err} -> Logger.error("Exchange error: \#{err.message}")
end
"""
alias CCXT.Error.Hints
alias CCXT.Error.Recoverability
@type error_type ::
:rate_limited
| :insufficient_balance
| :invalid_credentials
| :invalid_parameters
| :order_not_found
| :invalid_order
| :market_closed
| :network_error
| :access_restricted
| :not_supported
| :circuit_open
| :exchange_error
@type t :: %__MODULE__{
type: error_type(),
code: String.t() | integer() | nil,
message: String.t(),
exchange: atom() | nil,
retry_after: non_neg_integer() | nil,
raw: map() | nil,
hints: [String.t()],
recoverable: boolean() | nil
}
defstruct [:type, :code, :message, :exchange, :retry_after, :raw, :recoverable, hints: []]
@doc """
Creates a rate limited error.
## Options
- `:retry_after` - Milliseconds until retry is allowed
- `:exchange` - Exchange atom (e.g., `:binance`)
- `:raw` - Original error response from exchange
"""
@spec rate_limited(keyword()) :: t()
def rate_limited(opts \\ []) do
build_error(:rate_limited, "Rate limit exceeded", opts)
end
@doc """
Creates an insufficient balance error.
"""
@spec insufficient_balance(keyword()) :: t()
def insufficient_balance(opts \\ []) do
build_error(:insufficient_balance, "Insufficient balance", opts)
end
@doc """
Creates an invalid credentials error.
"""
@spec invalid_credentials(keyword()) :: t()
def invalid_credentials(opts \\ []) do
build_error(:invalid_credentials, "Invalid API credentials", opts)
end
@doc """
Creates an order not found error.
"""
@spec order_not_found(keyword()) :: t()
def order_not_found(opts \\ []) do
build_error(:order_not_found, "Order not found", opts)
end
@doc """
Creates an invalid order error.
"""
@spec invalid_order(keyword()) :: t()
def invalid_order(opts \\ []) do
build_error(:invalid_order, "Invalid order parameters", opts)
end
@doc """
Creates an invalid parameters error.
"""
@spec invalid_parameters(keyword()) :: t()
def invalid_parameters(opts \\ []) do
build_error(:invalid_parameters, "Invalid request parameters", opts)
end
@doc """
Creates a market closed error.
"""
@spec market_closed(keyword()) :: t()
def market_closed(opts \\ []) do
build_error(:market_closed, "Market is closed", opts)
end
@doc """
Creates a network error.
"""
@spec network_error(keyword()) :: t()
def network_error(opts \\ []) do
build_error(:network_error, "Network error", opts)
end
@doc """
Creates an access restricted error.
Used when exchange returns HTML instead of JSON, typically indicating
geographic/IP restrictions, Cloudflare challenges, or access blocks.
"""
@spec access_restricted(keyword()) :: t()
def access_restricted(opts \\ []) do
build_error(:access_restricted, "Access restricted - exchange returned HTML instead of JSON", opts)
end
@doc """
Creates a not supported error.
Used when a method exists in CCXT's capability list but cannot actually be
called for this exchange class (e.g., inherited methods that only work for
certain market types).
"""
@spec not_supported(keyword()) :: t()
def not_supported(opts \\ []) do
build_error(:not_supported, "Method not supported by this exchange", opts)
end
@doc """
Creates a circuit breaker open error.
Used when an exchange's circuit breaker has tripped due to multiple consecutive
failures. Requests are rejected fast to prevent cascade failures.
## Options
- `:exchange` - Exchange atom (e.g., `:binance`)
"""
@spec circuit_open(keyword()) :: t()
def circuit_open(opts \\ []) do
build_error(:circuit_open, "Circuit breaker is open", opts)
end
@doc """
Creates a generic exchange error.
Use this for errors that don't fit other categories.
"""
@spec exchange_error(String.t(), keyword()) :: t()
def exchange_error(message, opts \\ []) do
build_error(:exchange_error, message, opts)
end
# ===========================================================================
# Private Helpers
# ===========================================================================
@doc false
# Builds an error struct with consistent field population.
# Merges user-provided hints with auto-generated hints and sets recoverability.
@spec build_error(error_type(), String.t(), keyword()) :: t()
defp build_error(type, default_message, opts) do
user_hints = Keyword.get(opts, :hints, [])
%__MODULE__{
type: type,
message: Keyword.get(opts, :message, default_message),
code: Keyword.get(opts, :code),
retry_after: Keyword.get(opts, :retry_after),
exchange: Keyword.get(opts, :exchange),
raw: Keyword.get(opts, :raw),
hints: Hints.merge_hints(user_hints, type, opts),
recoverable: Recoverability.for_type(type)
}
end
end