hpyhacking/peatio · error · APIv2::CreateOrderError

2002

2002

Error message

Failed to create order. Reason: #{e}

What it means

Error code 2002 (CreateOrderError) is raised by APIv2::Helpers#create_order (POST /api/v2/orders). build_order constructs an OrderBid/OrderAsk (market taken from params[:market], ord_type defaulting to 'limit') and Ordering#submit executes it; a bare rescue catches EVERY exception and re-raises it wrapped as 'Failed to create order. Reason: <original exception>'. The real cause lives only in the Reason text and the server's 'Failed to create order:' log line.

Source

Thrown at app/api/api_v2/helpers.rb:53

        ask:           current_market.base_unit,
        bid:           current_market.quote_unit,
        currency:      current_market.id,
        ord_type:      attrs[:ord_type] || 'limit',
        price:         attrs[:price],
        volume:        attrs[:volume],
        origin_volume: attrs[:volume]
      )
    end

    def create_order(attrs)
      order = build_order attrs
      Ordering.new(order).submit
      order
    rescue
      Rails.logger.info "Failed to create order: #{$!}"
      Rails.logger.debug order.inspect
      Rails.logger.debug $!.backtrace.join("\n")
      raise CreateOrderError, $!
    end

    def create_orders(multi_attrs)
      orders = multi_attrs.map {|attrs| build_order attrs }
      Ordering.new(orders).submit
      orders
    rescue
      Rails.logger.info "Failed to create order: #{$!}"
      Rails.logger.debug $!.backtrace.join("\n")
      raise CreateOrderError, $!
    end

    def order_param
      params[:order_by].downcase == 'asc' ? 'id asc' : 'id desc'
    end

    def format_ticker(ticker)
      { at: ticker[:at],

View on GitHub (pinned to dab8641137)

Solutions

  1. Read the Reason in the 2002 message plus the matching 'Failed to create order:' server log line — it names the actual exception; fix that root cause first
  2. Pre-flight the order: GET /api/v2/markets to validate the market id and its precision, then check available (not total) balance covers price*volume
  3. Normalize price and volume to plain decimal strings at market precision and require both to be positive before submitting

Example fix

# before — fire and hope
post '/api/v2/orders', params: auth_params.merge(market: 'btcusd', side: 'buy', volume: 1, price: 4000)

# after — validate then submit, decode the wrapped reason
market = markets.find { |m| m['id'] == 'btcusd' } or raise 'unknown market'
raise 'insufficient funds' unless available_quote >= 4000 * 1
begin
  post '/api/v2/orders', params: signed(market: 'btcusd', side: 'buy', volume: '1.0', price: '4000.0')
rescue CreateOrderError => e
  reason = e.message.gsub('Failed to create order. Reason: ', '')
  retry_after_top_up if reason.include?('balance')
end
Defensive patterns

Strategy: try-catch

Validate before calling

# Pre-flight checks mirroring build_order + Ordering requirements
def order_submittable?(attrs, markets, accounts)
  m = markets.find { |x| x['id'] == attrs[:market] } or return false
  return false unless %w[buy sell].include?(attrs[:side])
  price = attrs[:price].to_d; volume = attrs[:volume].to_d
  return false unless price.positive? && volume.positive?
  cost = attrs[:side] == 'buy' ? price * volume : volume
  funds = attrs[:side] == 'buy' ? accounts[m['quote']] : accounts[m['base']]
  funds && funds >= cost
end

Try / catch

rescue the 2002 body, strip the 'Failed to create order. Reason: ' prefix, and branch on the underlying reason: balance errors trigger a top-up/queue path, validation errors mark the order template invalid, infrastructure reasons go to a bounded retry with fresh tonce and re-signature.

Prevention

When it happens

Trigger: Insufficient or locked balance for price*volume on a bid (volume on an ask); an unknown :market id (Market.find fails before the order is built); nil, zero, negative, or wrong-precision price/volume; ordering-service, AMQP, or database failures — all of these surface identically as 2002.

Common situations: Open orders already locked the funds so available balance is lower than total; market string mismatch such as 'btcusd' vs 'btcusdt'; price sent as a localized string ('1.234,5'); precision/lot-size rules changed after the client was written; a NoMethodError from nil price masked by the blanket rescue.

Related errors


AI-assisted analysis of hpyhacking/peatio@dab8641137 (2026-08-23). Data as JSON: /api/errors/7d40a3124d1ee457. Report an issue: GitHub.