infertux/ubea

View on GitHub
lib/ubea/exchanges/hit_btc_base.rb

Summary

Maintainability
A
35 mins
Test Coverage
module Ubea
  module Exchange
    class HitBtcBase < Base
      # NOTE: https://hitbtc-com.github.io/hitbtc-api/

      def name
        "HitBTC (#{fiat_currency})"
      end

      def exchange_settings
        raise NotImplementedError
      end

      def fiat_currency
        "EUR"
      end

      def trade_fee
        BigDecimal.new("0.001").freeze # 0.1% - see https://hitbtc.com/fees-and-limits
      end

      def refresh_order_book!
        json = get_json("https://api.hitbtc.com/api/1/public/BTC#{fiat_currency}/orderbook") or return

        asks = format_asks_bids(json["asks"])
        bids = format_asks_bids(json["bids"])

        mark_as_refreshed
        @order_book = OrderBook.new(asks: asks, bids: bids)
      end

      def balance
        balance = get_private("trading/balance")

        balances = balance["balance"]
        if balances.nil?
          p balance
          raise
        end
        fiat = balances.detect { |bal| bal["currency_code"] == fiat_currency }["cash"]
        xbt = balances.detect { |bal| bal["currency_code"] == "BTC" }["cash"]

        OpenStruct.new(
          fiat: Money.new(fiat.to_s, fiat_currency),
          xbt: BigDecimal.new(xbt.to_s),
        ).freeze
      end

      def trade!(args)
        params = {
          clientOrderId: SecureRandom.hex(32),
          symbol: "BTC#{fiat_currency}",
          side: args.fetch(:type),
          price: args.fetch(:price),
          quantity: (BigDecimal.new(args.fetch(:volume)) * 100).to_f.to_s,
          type: "limit",
        }

        Log.debug params
        trade = post_private("trading/new_order", params)
        Log.info trade
      end

      def open_orders
        get_private("trading/orders/active")["orders"]
      end

      def open_orders?
        !open_orders.empty?
      end

    private

      def format_asks_bids(json)
        json.map do |price, volume|
          Offer.new(
            price: Money.new(price, fiat_currency),
            volume: volume
          ).freeze
        end
      end

      def get_private(method, params = {})
        request_private :get, method, params
      end

      def post_private(method, params = {})
        request_private :post, method, params
      end

      def request_private(request_type, method, params = {})
        params['nonce'] = nonce
        params['apikey'] = exchange_settings[:api_key]

        response = http_adapter("http://api.hitbtc.com/api/1") \
                   .public_send(request_type, url_path(method), params) do |request|
          request.headers['X-Signature'] = generate_signature(method, params)
        end

        json = JSON.parse(response.body)
        if json.key? "code"
          p json
          raise "OOPS"
        end

        json
      end

      def nonce
        now = Time.now.to_f

        if (@nonce || 0) + 1 > now.to_i
          Log.warn "Throttling API call for 1s to #{self}"
          sleep 1
          now = Time.now.to_f
        end

        @nonce = now.to_i

        sprintf("%.3f", now).sub(".", "")
      end

      def generate_signature(method, params)
        key = exchange_settings[:api_secret]
        message = generate_message(method, params)
        generate_hmac(key, message)
      end

      def generate_message(method, params)
        url_path(method) + encode_params(params)
      end

      def generate_hmac(key, message)
        OpenSSL::HMAC.hexdigest('sha512', key, message)
      end

      def encode_params(params)
        uri = Addressable::URI.new
        uri.query_values = params
        "?" + uri.query
      end

      def url_path(method)
        '/api/1/' + method
      end
    end
  end
end