lib/cryptum/api.rb in cryptum-0.0.380 vs lib/cryptum/api.rb in cryptum-0.0.381
- old
+ new
@@ -7,620 +7,30 @@
require 'rest-client'
module Cryptum
# This plugin is used to interact withbtje Coinbase REST API
module API
- # Supported Method Parameters::
- # Cryptum::API.generate_signature(
- # )
+ require 'cryptum/api/exchange_rates'
+ require 'cryptum/api/fees'
+ require 'cryptum/api/order_history'
+ require 'cryptum/api/orders'
+ require 'cryptum/api/portfolio'
+ require 'cryptum/api/products'
+ require 'cryptum/api/rest'
+ require 'cryptum/api/signature'
- public_class_method def self.generate_signature(opts = {})
- api_secret = opts[:api_secret]
-
- http_method = if opts[:http_method].nil?
- :GET
- else
- opts[:http_method].to_s.upcase.scrub.strip.chomp.to_sym
- end
-
- api_call = opts[:api_call].to_s.scrub.strip.chomp
- api_call = '/users/self/verify' if opts[:api_call].nil?
-
- if opts[:params].nil?
- path = api_call
- else
- uri = Addressable::URI.new
- uri.query_values = opts[:params]
- params = uri.query
- path = "#{api_call}?#{params}"
- end
-
- http_body = opts[:http_body].to_s.scrub.strip.chomp
-
- api_timestamp = Time.now.utc.to_i.to_s
-
- api_signature = Base64.strict_encode64(
- OpenSSL::HMAC.digest(
- 'sha256',
- Base64.strict_decode64(api_secret),
- "#{api_timestamp}#{http_method}#{path}#{http_body}"
- )
- )
-
- if http_body == ''
- api_signature = Base64.strict_encode64(
- OpenSSL::HMAC.digest(
- 'sha256',
- Base64.strict_decode64(api_secret),
- "#{api_timestamp}#{http_method}#{path}"
- )
- )
- end
-
- api_signature_response = {}
- api_signature_response[:api_timestamp] = api_timestamp
- api_signature_response[:api_signature] = api_signature
-
- api_signature_response
- rescue RestClient::ExceptionWithResponse => e
- File.open('/tmp/cryptum-errors.txt', 'a') do |f|
- f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
- f.puts "Module: #{self}"
- f.puts "URL: #{api_endpoint}#{api_call}"
- f.puts "PARAMS: #{params.inspect}"
- f.puts "HTTP POST BODY: #{http_body.inspect}" if http_body != ''
- f.puts "#{e}\n#{e.response}\n\n\n"
- end
- rescue StandardError => e
- raise e
- end
-
- private_class_method def self.rest_api_call(opts = {})
- env = opts[:env]
- option_choice = opts[:option_choice]
- order_type = opts[:order_type]
- event_notes = opts[:event_notes]
- api_endpoint = opts[:api_endpoint]
- base_increment = opts[:base_increment].to_f
-
- api_key = env[:api_key]
- api_secret = env[:api_secret]
- api_passphrase = env[:api_passphrase]
- api_endpoint = 'https://api.exchange.coinbase.com'
- api_endpoint = 'https://api-public.sandbox.exchange.coinbase.com' if env[:env] == :sandbox
- api_endpoint = opts[:api_endpoint] if opts[:api_endpoint]
-
- http_method = if opts[:http_method].nil?
- :GET
- else
- opts[:http_method].to_s.upcase.scrub.strip.chomp.to_sym
- end
- api_call = opts[:api_call].to_s.scrub
- params = opts[:params]
- http_body = opts[:http_body].to_s.scrub.strip.chomp
-
- max_conn_attempts = 30
- conn_attempt = 0
-
- begin
- conn_attempt += 1
- if option_choice.proxy
- rest_client = RestClient
- rest_client.proxy = option_choice.proxy
- rest_client_request = rest_client::Request
- else
- rest_client_request = RestClient::Request
- end
-
- api_signature_response = generate_signature(
- api_secret: api_secret,
- http_method: http_method,
- api_call: api_call,
- params: params,
- http_body: http_body
- )
- api_signature = api_signature_response[:api_signature]
- api_timestamp = api_signature_response[:api_timestamp]
-
- case http_method
- when :GET
- headers = {
- content_type: 'application/json; charset=UTF-8',
- CB_ACCESS_TIMESTAMP: api_timestamp,
- CB_ACCESS_PASSPHRASE: api_passphrase,
- CB_ACCESS_KEY: api_key,
- CB_ACCESS_SIGN: api_signature
- }
-
- headers[:params] = params if params
- headers[:ORDER_TYPE] = order_type if order_type
- headers[:EVENT_NOTES] = event_notes if event_notes
-
- response = rest_client_request.execute(
- method: :GET,
- url: "#{api_endpoint}#{api_call}",
- headers: headers,
- verify_ssl: false
- )
-
- when :DELETE
- headers = {
- content_type: 'application/json; charset=UTF-8',
- CB_ACCESS_TIMESTAMP: api_timestamp,
- CB_ACCESS_PASSPHRASE: api_passphrase,
- CB_ACCESS_KEY: api_key,
- CB_ACCESS_SIGN: api_signature
- }
-
- headers[:params] = params if params
- headers[:ORDER_TYPE] = order_type if order_type
- headers[:EVENT_NOTES] = event_notes if event_notes
-
- response = rest_client_request.execute(
- method: :DELETE,
- url: "#{api_endpoint}#{api_call}",
- headers: headers,
- verify_ssl: false
- )
-
- when :POST
- headers = {
- content_type: 'application/json; charset=UTF-8',
- CB_ACCESS_TIMESTAMP: api_timestamp,
- CB_ACCESS_PASSPHRASE: api_passphrase,
- CB_ACCESS_KEY: api_key,
- CB_ACCESS_SIGN: api_signature
- }
-
- headers[:params] = params if params
- headers[:ORDER_TYPE] = order_type if order_type
- headers[:EVENT_NOTES] = event_notes if event_notes
-
- response = rest_client_request.execute(
- method: :POST,
- url: "#{api_endpoint}#{api_call}",
- headers: headers,
- payload: http_body,
- verify_ssl: false
- )
-
- else
- raise @@logger.error("Unsupported HTTP Method #{http_method} for #{self} Plugin")
- end
-
- JSON.parse(response, symbolize_names: true)
- rescue RestClient::Unauthorized => e
- File.open('/tmp/cryptum-errors.txt', 'a') do |f|
- f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
- f.puts "#{self}\n#{e}\n\n\n"
- end
-
- raise e if conn_attempt > max_conn_attempts
-
- sleep 60
- retry
- end
- rescue RestClient::ExceptionWithResponse => e
- File.open('/tmp/cryptum-errors.txt', 'a') do |f|
- f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
- f.puts "Module: #{self}"
- f.puts "URL: #{api_endpoint}#{api_call}"
- f.puts "PARAMS: #{params.inspect}"
- f.puts "HTTP POST BODY: #{http_body.inspect}" if http_body != ''
- f.puts "#{e}\n#{e.response}\n\n\n"
- end
-
- insufficient_funds = '{"message":"Insufficient funds"}'
- size -= base_increment if e.response == insufficient_funds
-
- sleep 0.3
- retry
- rescue RestClient::TooManyRequests => e
- File.open('/tmp/cryptum-errors.txt', 'a') do |f|
- f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
- f.puts "Module: #{self}"
- f.puts "URL: #{api_endpoint}#{api_call}"
- f.puts "PARAMS: #{params.inspect}"
- f.puts "HTTP POST BODY: #{http_body.inspect}" if http_body != ''
- f.puts "#{e}\n#{e.response}\n\n\n"
- end
-
- sleep 1
- retry
- end
-
- public_class_method def self.submit_limit_order(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
- price = opts[:price]
- size = opts[:size]
- buy_or_sell = opts[:buy_or_sell]
- event_history = opts[:event_history]
- bot_conf = opts[:bot_conf]
- buy_order_id = opts[:buy_order_id]
-
- tpm = bot_conf[:target_profit_margin_percent].to_f
- tpm_cast_as_decimal = tpm / 100
-
- product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
-
- this_product = event_history.order_book[:this_product]
- base_increment = this_product[:base_increment]
- quote_increment = this_product[:quote_increment]
- # crypto_smallest_decimal = base_increment.to_s.split('.')[-1].length
- fiat_smallest_decimal = quote_increment.to_s.split('.')[-1].length
-
- order_hash = {}
-
- order_hash[:type] = 'limit'
- order_hash[:time_in_force] = 'GTC'
-
- if buy_or_sell == :buy
- order_hash[:time_in_force] = 'GTT'
- order_hash[:cancel_after] = 'min'
- end
-
- order_hash[:size] = size
- order_hash[:price] = price
- order_hash[:side] = buy_or_sell
- order_hash[:product_id] = product_id
-
- http_body = order_hash.to_json
-
- limit_order_resp = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :POST,
- api_call: '/orders',
- http_body: http_body,
- base_increment: base_increment
- )
-
- # Populate Order ID on the Buy
- # to know what to do on the Sell
- case buy_or_sell
- when :buy
- this_order = event_history.order_book[:order_plan].shift
- this_order[:buy_order_id] = limit_order_resp[:id]
- this_order[:price] = price
- targ_price = price.to_f + (price.to_f * tpm_cast_as_decimal)
- this_order[:tpm] = format(
- '%0.2f',
- tpm
- )
- this_order[:target_price] = format(
- "%0.#{fiat_smallest_decimal}f",
- targ_price
- )
-
- this_order[:size] = size
-
- this_order[:color] = :cyan
- event_history.order_book[:order_history_meta].push(this_order)
- when :sell
- sell_order_id = limit_order_resp[:id]
- event_history.order_book[:order_history_meta].each do |meta|
- if meta[:buy_order_id] == buy_order_id
- meta[:sell_order_id] = sell_order_id
- meta[:color] = :yellow
- end
- end
- end
-
- event_history
- rescue StandardError => e
- raise e
- end
-
- public_class_method def self.gtfo(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
- event_history = opts[:event_history]
-
- # Cancel all open orders
- cancel_all_open_orders(
- env: env,
- option_choice: option_choice
- )
-
- product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
-
- this_product = event_history.order_book[:this_product]
- base_increment = this_product[:base_increment]
- quote_increment = this_product[:quote_increment]
- crypto_smallest_decimal = base_increment.to_s.split('.')[-1].length
- fiat_smallest_decimal = quote_increment.to_s.split('.')[-1].length
-
- # TODO: Calculate / Price / Size
- last_three_prices_arr = []
- last_ticker_price = event_history.order_book[:ticker_price].to_f
- second_to_last_ticker_price = event_history.order_book[:ticker_price_second_to_last].to_f
- third_to_last_ticker_price = event_history.order_book[:ticker_price_third_to_last].to_f
- last_three_prices_arr.push(last_ticker_price)
- last_three_prices_arr.push(second_to_last_ticker_price)
- last_three_prices_arr.push(third_to_last_ticker_price)
- limit_price = last_three_prices_arr.sort[1]
- price = format(
- "%0.#{fiat_smallest_decimal}f",
- limit_price
- )
-
- crypto_currency = option_choice.symbol.to_s.upcase.split('_').first.to_sym
- portfolio = event_history.order_book[:portfolio]
- this_account = portfolio.select do |account|
- account[:currency] == crypto_currency.to_s
- end
- balance = format(
- "%0.#{crypto_smallest_decimal}f",
- this_account.first[:balance]
- )
-
- current_crypto_fiat_value = format(
- '%0.2f',
- balance.to_f * price.to_f
- )
-
- order_hash = {}
-
- order_hash[:type] = 'limit'
- order_hash[:time_in_force] = 'GTT'
- order_hash[:cancel_after] = 'min'
-
- order_hash[:size] = balance
- order_hash[:price] = price
- order_hash[:side] = :sell
- order_hash[:product_id] = product_id
-
- http_body = order_hash.to_json
-
- limit_order_resp = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :POST,
- api_call: '/orders',
- http_body: http_body,
- base_increment: base_increment
- )
-
- # Populate Order ID on the Buy
- # to know what to do on the Sell
- this_order = {}
- this_order[:plan_no] = '0.0'
- this_order[:fiat_available] = '0.00'
- this_order[:risk_alloc] = current_crypto_fiat_value
- this_order[:allocation_decimal] = '1.0'
- this_order[:allocation_percent] = '100.0'
- this_order[:invest] = current_crypto_fiat_value
- this_order[:return] = current_crypto_fiat_value
- this_order[:profit] = '0.0'
- this_order[:buy_order_id] = 'N/A'
- this_order[:price] = price
- this_order[:tpm] = '0.00'
- this_order[:target_price] = current_crypto_fiat_value
- this_order[:size] = balance
- this_order[:color] = :magenta
- this_order[:sell_order_id] = limit_order_resp[:id]
-
- event_history.order_book[:order_history_meta].push(this_order)
-
- event_history
- rescue StandardError => e
- raise e
- end
-
- # public_class_method def self.cancel_open_order(opts = {})
- # env = opts[:env]
- # option_choice = opts[:option_choice]
- # order_id = opts[:order_id]
- # order_type = opts[:order_type]
-
- # product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
-
- # order_hash = {}
- # order_hash[:product_id] = product_id
-
- # params = order_hash
-
- # rest_api_call(
- # env: env,
- # http_method: :DELETE,
- # api_call: "/orders/#{order_id}",
- # option_choice: option_choice,
- # params: params,
- # order_type: order_type
- # )
- # rescue StandardError => e
- # raise e
- # end
-
- public_class_method def self.cancel_all_open_orders(opts = {})
- env = opts[:env]
- option_choice = opts[:option_choice]
- event_notes = opts[:event_notes]
-
- product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
-
- order_hash = {}
- order_hash[:product_id] = product_id
-
- # http_body = order_hash.to_json
- params = order_hash
-
- canceled_order_id_arr = []
- loop do
- canceled_order_id_arr = rest_api_call(
- env: env,
- http_method: :DELETE,
- api_call: '/orders',
- option_choice: option_choice,
- params: params,
- event_notes: event_notes
- )
-
- break if canceled_order_id_arr.empty?
- end
- canceled_order_id_arr
- rescue StandardError => e
- raise e
- end
-
- public_class_method def self.get_products(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
-
- products = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :GET,
- api_call: '/products'
- )
-
- if products.length.positive?
- supported_products_filter = products.select do |product|
- product[:id].match?(/USD$/) &&
- product[:status] == 'online' &&
- product[:fx_stablecoin] == false
- end
- sorted_products = supported_products_filter.sort_by { |hash| hash[:id] }
- end
-
- sorted_products
- rescue StandardError => e
- raise e
- end
-
- # List Supported Cryptum Products and Exit
- public_class_method def self.list_products_and_exit(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
-
- puts "\n#{option_choice.driver_name} Supports the Following Products:"
- products = Cryptum::API.get_products(
- option_choice: option_choice,
- env: env
- )
-
- products.map do |product|
- puts product[:id].downcase
- end
-
- exit 0
- rescue StandardError => e
- raise e
- end
-
- private_class_method def self.get_exchange_rates(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
-
- api_endpoint = 'https://api.coinbase.com/v2'
- exchange_rates_api_call = '/exchange-rates'
-
- # We don't always get fees back from Coinbase...
- # This is a hack to ensure we do.
- exchange = {}
- exchange = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :GET,
- api_endpoint: api_endpoint,
- api_call: exchange_rates_api_call
- )
-
- exchange[:data][:rates]
- rescue StandardError => e
- raise e
- end
-
- public_class_method def self.get_portfolio(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
- crypto = opts[:crypto]
- fiat = opts[:fiat]
- fiat_portfolio_file = opts[:fiat_portfolio_file]
- event_notes = opts[:event_notes]
-
- # Retrieve Exchange Rates
- exchange_rates = get_exchange_rates(
- option_choice: option_choice,
- env: env
- )
-
- portfolio_complete_arr = []
- portfolio_complete_arr = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :GET,
- api_call: '/accounts',
- event_notes: event_notes
- )
-
- all_products = portfolio_complete_arr.select do |products|
- products if products[:balance].to_f.positive?
- end
-
- total_holdings = 0.00
- all_products.each do |product|
- currency = product[:currency].to_sym
- this_exchange_rate = exchange_rates[currency].to_f
- total_holdings += product[:balance].to_f / this_exchange_rate
- end
-
- crypto_portfolio = portfolio_complete_arr.select do |product|
- product if product[:currency] == crypto
- end
-
- fiat_portfolio = portfolio_complete_arr.select do |product|
- product if product[:currency] == fiat
- end
- fiat_portfolio.last[:total_holdings] = format(
- '%0.8f',
- total_holdings
- )
-
- File.write(
- fiat_portfolio_file,
- JSON.pretty_generate(fiat_portfolio)
- )
-
- crypto_portfolio
- rescue StandardError => e
- raise e
- end
-
- public_class_method def self.get_fees(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
-
- fees_api_call = '/fees'
-
- # We don't always get fees back from Coinbase...
- # This is a hack to ensure we do.
- fees = {}
- fees = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :GET,
- api_call: fees_api_call
- )
-
- fees
- rescue StandardError => e
- raise e
- end
-
# public_class_method def self.get_profiles(opts = {})
# option_choice = opts[:option_choice]
# env = opts[:env]
# profiles_api_call = '/profiles'
# # We don't always get fees back from Coinbase...
# # This is a hack to ensure we do.
# profiles = {}
# # loop do
- # profiles = rest_api_call(
+ # profiles = Cryptum::API::Rest.call(
# option_choice: option_choice,
# env: env,
# http_method: :GET,
# api_call: profiles_api_call
# )
@@ -633,74 +43,14 @@
# profiles
# rescue StandardError => e
# raise e
# end
- public_class_method def self.get_order_history(opts = {})
- option_choice = opts[:option_choice]
- env = opts[:env]
-
- product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
-
- orders_api_call = '/orders'
- params = {}
- params[:product_id] = product_id
- params[:status] = 'all'
-
- # We don't always get order_history back from Coinbase...
- # This is a hack to ensure we do.
- order_history = []
- order_history = rest_api_call(
- option_choice: option_choice,
- env: env,
- http_method: :GET,
- api_call: orders_api_call,
- params: params
- )
-
- # Cast UTC Timestamps as local times
- order_history.each do |order|
- order[:created_at] = Time.parse(
- order[:created_at]
- ).localtime.to_s
-
- next unless order[:done_at]
-
- order[:done_at] = Time.parse(
- order[:done_at]
- ).localtime.to_s
- end
-
- order_history
- rescue StandardError => e
- raise e
- end
-
# Display Usage for this Module
public_class_method def self.help
puts "USAGE:
- signature = #{self}.generate_signature(
- api_secret: 'required - Coinbase Pro API Secret',
- http_method: 'optional - Defaults to :GET',
- api_call: 'optional - Defaults to /users/self/verify',
- params: 'optional - HTTP GET Parameters',
- http_body: 'optional HTTP POST Body'
- )
-
profiles = #{self}.get_profiles(
- env: 'required - Coinbase::Option::Environment.get Object'
- )
-
- products = #{self}.get_products(
- env: 'required - Coinbase::Option::Environment.get Object'
- )
-
- portfolio = #{self}.get_portfolio(
- env: 'required - Coinbase::Option::Environment.get Object'
- )
-
- order_history = #{self}.get_order_history(
env: 'required - Coinbase::Option::Environment.get Object'
)
"
end
end