{"record":{"id":"7d40a3124d1ee457","repo":"hpyhacking/peatio","slug":"2002","errorCode":"2002","errorMessage":"Failed to create order. Reason: #{e}","messagePattern":"Failed to create order\\. Reason: #(.+?)","errorType":"exception","errorClass":"APIv2::CreateOrderError","httpStatus":400,"severity":"error","filePath":"app/api/api_v2/helpers.rb","lineNumber":53,"sourceCode":"        ask:           current_market.base_unit,\n        bid:           current_market.quote_unit,\n        currency:      current_market.id,\n        ord_type:      attrs[:ord_type] || 'limit',\n        price:         attrs[:price],\n        volume:        attrs[:volume],\n        origin_volume: attrs[:volume]\n      )\n    end\n\n    def create_order(attrs)\n      order = build_order attrs\n      Ordering.new(order).submit\n      order\n    rescue\n      Rails.logger.info \"Failed to create order: #{$!}\"\n      Rails.logger.debug order.inspect\n      Rails.logger.debug $!.backtrace.join(\"\\n\")\n      raise CreateOrderError, $!\n    end\n\n    def create_orders(multi_attrs)\n      orders = multi_attrs.map {|attrs| build_order attrs }\n      Ordering.new(orders).submit\n      orders\n    rescue\n      Rails.logger.info \"Failed to create order: #{$!}\"\n      Rails.logger.debug $!.backtrace.join(\"\\n\")\n      raise CreateOrderError, $!\n    end\n\n    def order_param\n      params[:order_by].downcase == 'asc' ? 'id asc' : 'id desc'\n    end\n\n    def format_ticker(ticker)\n      { at: ticker[:at],","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/hpyhacking/peatio/blob/dab8641137c008928c835409342519bfef4eae7f/app/api/api_v2/helpers.rb#L35-L71","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","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","Normalize price and volume to plain decimal strings at market precision and require both to be positive before submitting"],"exampleFix":"# before — fire and hope\npost '/api/v2/orders', params: auth_params.merge(market: 'btcusd', side: 'buy', volume: 1, price: 4000)\n\n# after — validate then submit, decode the wrapped reason\nmarket = markets.find { |m| m['id'] == 'btcusd' } or raise 'unknown market'\nraise 'insufficient funds' unless available_quote >= 4000 * 1\nbegin\n  post '/api/v2/orders', params: signed(market: 'btcusd', side: 'buy', volume: '1.0', price: '4000.0')\nrescue CreateOrderError => e\n  reason = e.message.gsub('Failed to create order. Reason: ', '')\n  retry_after_top_up if reason.include?('balance')\nend","handlingStrategy":"try-catch","validationCode":"# Pre-flight checks mirroring build_order + Ordering requirements\ndef order_submittable?(attrs, markets, accounts)\n  m = markets.find { |x| x['id'] == attrs[:market] } or return false\n  return false unless %w[buy sell].include?(attrs[:side])\n  price = attrs[:price].to_d; volume = attrs[:volume].to_d\n  return false unless price.positive? && volume.positive?\n  cost = attrs[:side] == 'buy' ? price * volume : volume\n  funds = attrs[:side] == 'buy' ? accounts[m['quote']] : accounts[m['base']]\n  funds && funds >= cost\nend","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["Check available (unlocked) balance, not total balance, before submitting","Fetch /api/v2/markets at startup and validate id, precision, and min volume against it","Send numbers as plain decimal strings to avoid float/locale surprises","Never assume 2002 means one thing — always decode the wrapped Reason"],"tags":["ruby","trading","orders","exchange","rescue-all"],"backgroundTag":"order-rejected-by-exchange","analyzedSha":"dab8641137c008928c835409342519bfef4eae7f","analyzedAt":"2026-08-23T09:59:18.005Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}