cryptum 0.0.359 → 0.0.361

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +16 -0
  3. data/.gitignore +30 -0
  4. data/.rspec +3 -0
  5. data/.rspec_status +0 -0
  6. data/.rubocop.yml +31 -0
  7. data/.rubocop_todo.yml +36 -0
  8. data/.ruby-gemset +1 -0
  9. data/.ruby-version +1 -0
  10. data/CODE_OF_CONDUCT.md +84 -0
  11. data/Gemfile +38 -0
  12. data/LICENSE +674 -0
  13. data/README.md +87 -0
  14. data/Rakefile +19 -0
  15. data/bin/cryptum +73 -0
  16. data/bin/cryptum-forecast +200 -0
  17. data/bin/cryptum-repl +73 -0
  18. data/bin/cryptum_autoinc_version +38 -0
  19. data/build_cryptum_gem.sh +58 -0
  20. data/cryptum.gemspec +52 -0
  21. data/cryptum_container.sh +1 -0
  22. data/docker/cryptum.json +60 -0
  23. data/docker/cryptum_container.sh +59 -0
  24. data/docker/packer_secrets.json.EXAMPLE +7 -0
  25. data/docker/provisioners/cryptum.sh +11 -0
  26. data/docker/provisioners/docker_bashrc.sh +2 -0
  27. data/docker/provisioners/docker_rvm.sh +22 -0
  28. data/docker/provisioners/init_image.sh +28 -0
  29. data/docker/provisioners/post_install.sh +6 -0
  30. data/docker/provisioners/ruby.sh +16 -0
  31. data/docker/provisioners/upload_globals.sh +49 -0
  32. data/etc/bot_confs/.gitkeep +0 -0
  33. data/etc/bot_confs/BOT_CONF.TEMPLATE +10 -0
  34. data/etc/coinbase_pro.yaml.EXAMPLE +8 -0
  35. data/etc/open_ai.yaml.EXAMPLE +1 -0
  36. data/git_commit.sh +22 -0
  37. data/lib/cryptum/api.rb +688 -0
  38. data/lib/cryptum/bot_conf.rb +197 -0
  39. data/lib/cryptum/event/bot_conf.rb +34 -0
  40. data/lib/cryptum/event/buy.rb +145 -0
  41. data/lib/cryptum/event/cancel.rb +35 -0
  42. data/lib/cryptum/event/exit.rb +35 -0
  43. data/lib/cryptum/event/gtfo.rb +36 -0
  44. data/lib/cryptum/event/history.rb +108 -0
  45. data/lib/cryptum/event/key_press.rb +64 -0
  46. data/lib/cryptum/event/order_book.rb +34 -0
  47. data/lib/cryptum/event/pane.rb +65 -0
  48. data/lib/cryptum/event/parse.rb +181 -0
  49. data/lib/cryptum/event/scroll.rb +200 -0
  50. data/lib/cryptum/event/sell.rb +124 -0
  51. data/lib/cryptum/event.rb +27 -0
  52. data/lib/cryptum/log.rb +34 -0
  53. data/lib/cryptum/matrix.rb +181 -0
  54. data/lib/cryptum/open_ai.rb +156 -0
  55. data/lib/cryptum/option/choice.rb +28 -0
  56. data/lib/cryptum/option.rb +206 -0
  57. data/lib/cryptum/order_book/generate.rb +114 -0
  58. data/lib/cryptum/order_book/indicator.rb +15 -0
  59. data/lib/cryptum/order_book/market_trend.rb +137 -0
  60. data/lib/cryptum/order_book/profit_margin.rb +55 -0
  61. data/lib/cryptum/order_book.rb +19 -0
  62. data/lib/cryptum/portfolio/balance.rb +123 -0
  63. data/lib/cryptum/portfolio.rb +15 -0
  64. data/lib/cryptum/ui/command.rb +314 -0
  65. data/lib/cryptum/ui/key_press_event.rb +33 -0
  66. data/lib/cryptum/ui/market_trend.rb +77 -0
  67. data/lib/cryptum/ui/order_execute_details.rb +297 -0
  68. data/lib/cryptum/ui/order_execution.rb +583 -0
  69. data/lib/cryptum/ui/order_plan.rb +512 -0
  70. data/lib/cryptum/ui/order_plan_details.rb +240 -0
  71. data/lib/cryptum/ui/order_timer.rb +136 -0
  72. data/lib/cryptum/ui/portfolio.rb +221 -0
  73. data/lib/cryptum/ui/signal_engine.rb +109 -0
  74. data/lib/cryptum/ui/terminal_window.rb +111 -0
  75. data/lib/cryptum/ui/ticker.rb +319 -0
  76. data/lib/cryptum/ui.rb +343 -0
  77. data/lib/cryptum/version.rb +5 -0
  78. data/lib/cryptum/web_sock/coinbase.rb +104 -0
  79. data/lib/cryptum/web_sock/event_machine.rb +276 -0
  80. data/lib/cryptum/web_sock.rb +16 -0
  81. data/lib/cryptum.rb +120 -0
  82. data/order_books/.gitkeep +0 -0
  83. data/reinstall_cryptum_gemset.sh +29 -0
  84. data/spec/lib/cryptum/api_spec.rb +10 -0
  85. data/spec/lib/cryptum/event_spec.rb +10 -0
  86. data/spec/lib/cryptum/log_spec.rb +10 -0
  87. data/spec/lib/cryptum/option_spec.rb +10 -0
  88. data/spec/lib/cryptum/order_book/generate_spec.rb +10 -0
  89. data/spec/lib/cryptum/order_book/market_trend_spec.rb +10 -0
  90. data/spec/lib/cryptum/order_book_spec.rb +10 -0
  91. data/spec/lib/cryptum/ui/command_spec.rb +10 -0
  92. data/spec/lib/cryptum/ui/ticker_spec.rb +10 -0
  93. data/spec/lib/cryptum/ui_spec.rb +10 -0
  94. data/spec/lib/cryptum/web_sock_spec.rb +10 -0
  95. data/spec/lib/cryptum_spec.rb +10 -0
  96. data/spec/spec_helper.rb +3 -0
  97. data/upgrade_Gemfile_gems.sh +20 -0
  98. data/upgrade_cryptum.sh +13 -0
  99. data/upgrade_gem.sh +4 -0
  100. data/upgrade_ruby.sh +45 -0
  101. metadata +113 -10
@@ -0,0 +1,688 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'addressable'
4
+ require 'base64'
5
+ require 'json'
6
+ require 'openssl'
7
+ require 'rest-client'
8
+
9
+ module Cryptum
10
+ # This plugin is used to interact withbtje Coinbase REST API
11
+ module API
12
+ # Supported Method Parameters::
13
+ # Cryptum::API.generate_signature(
14
+ # )
15
+
16
+ public_class_method def self.generate_signature(opts = {})
17
+ api_secret = opts[:api_secret]
18
+
19
+ http_method = if opts[:http_method].nil?
20
+ :GET
21
+ else
22
+ opts[:http_method].to_s.upcase.scrub.strip.chomp.to_sym
23
+ end
24
+
25
+ api_call = opts[:api_call].to_s.scrub.strip.chomp
26
+ api_call = '/users/self/verify' if opts[:api_call].nil?
27
+
28
+ if opts[:params].nil?
29
+ path = api_call
30
+ else
31
+ uri = Addressable::URI.new
32
+ uri.query_values = opts[:params]
33
+ params = uri.query
34
+ path = "#{api_call}?#{params}"
35
+ end
36
+
37
+ http_body = opts[:http_body].to_s.scrub.strip.chomp
38
+
39
+ api_timestamp = Time.now.utc.to_i.to_s
40
+
41
+ api_signature = Base64.strict_encode64(
42
+ OpenSSL::HMAC.digest(
43
+ 'sha256',
44
+ Base64.strict_decode64(api_secret),
45
+ "#{api_timestamp}#{http_method}#{path}#{http_body}"
46
+ )
47
+ )
48
+
49
+ if http_body == ''
50
+ api_signature = Base64.strict_encode64(
51
+ OpenSSL::HMAC.digest(
52
+ 'sha256',
53
+ Base64.strict_decode64(api_secret),
54
+ "#{api_timestamp}#{http_method}#{path}"
55
+ )
56
+ )
57
+ end
58
+
59
+ api_signature_response = {}
60
+ api_signature_response[:api_timestamp] = api_timestamp
61
+ api_signature_response[:api_signature] = api_signature
62
+
63
+ api_signature_response
64
+ rescue RestClient::ExceptionWithResponse => e
65
+ File.open('/tmp/cryptum-errors.txt', 'a') do |f|
66
+ f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
67
+ f.puts "Module: #{self}"
68
+ f.puts "URL: #{api_endpoint}#{api_call}"
69
+ f.puts "PARAMS: #{params.inspect}"
70
+ f.puts "HTTP POST BODY: #{http_body.inspect}" if http_body != ''
71
+ f.puts "#{e}\n#{e.response}\n\n\n"
72
+ end
73
+ rescue StandardError => e
74
+ raise e
75
+ end
76
+
77
+ private_class_method def self.rest_api_call(opts = {})
78
+ env = opts[:env]
79
+ option_choice = opts[:option_choice]
80
+ order_type = opts[:order_type]
81
+ event_notes = opts[:event_notes]
82
+ api_endpoint = opts[:api_endpoint]
83
+ base_increment = opts[:base_increment].to_f
84
+
85
+ api_key = env[:api_key]
86
+ api_secret = env[:api_secret]
87
+ api_passphrase = env[:api_passphrase]
88
+ api_endpoint = 'https://api.exchange.coinbase.com'
89
+ api_endpoint = 'https://api-public.sandbox.exchange.coinbase.com' if env[:env] == :sandbox
90
+ api_endpoint = opts[:api_endpoint] if opts[:api_endpoint]
91
+
92
+ http_method = if opts[:http_method].nil?
93
+ :GET
94
+ else
95
+ opts[:http_method].to_s.upcase.scrub.strip.chomp.to_sym
96
+ end
97
+ api_call = opts[:api_call].to_s.scrub
98
+ params = opts[:params]
99
+ http_body = opts[:http_body].to_s.scrub.strip.chomp
100
+
101
+ max_conn_attempts = 30
102
+ conn_attempt = 0
103
+
104
+ begin
105
+ conn_attempt += 1
106
+ if option_choice.proxy
107
+ rest_client = RestClient
108
+ rest_client.proxy = option_choice.proxy
109
+ rest_client_request = rest_client::Request
110
+ else
111
+ rest_client_request = RestClient::Request
112
+ end
113
+
114
+ api_signature_response = generate_signature(
115
+ api_secret: api_secret,
116
+ http_method: http_method,
117
+ api_call: api_call,
118
+ params: params,
119
+ http_body: http_body
120
+ )
121
+ api_signature = api_signature_response[:api_signature]
122
+ api_timestamp = api_signature_response[:api_timestamp]
123
+
124
+ case http_method
125
+ when :GET
126
+ headers = {
127
+ content_type: 'application/json; charset=UTF-8',
128
+ CB_ACCESS_TIMESTAMP: api_timestamp,
129
+ CB_ACCESS_PASSPHRASE: api_passphrase,
130
+ CB_ACCESS_KEY: api_key,
131
+ CB_ACCESS_SIGN: api_signature
132
+ }
133
+
134
+ headers[:params] = params if params
135
+ headers[:ORDER_TYPE] = order_type if order_type
136
+ headers[:EVENT_NOTES] = event_notes if event_notes
137
+
138
+ response = rest_client_request.execute(
139
+ method: :GET,
140
+ url: "#{api_endpoint}#{api_call}",
141
+ headers: headers,
142
+ verify_ssl: false
143
+ )
144
+
145
+ when :DELETE
146
+ headers = {
147
+ content_type: 'application/json; charset=UTF-8',
148
+ CB_ACCESS_TIMESTAMP: api_timestamp,
149
+ CB_ACCESS_PASSPHRASE: api_passphrase,
150
+ CB_ACCESS_KEY: api_key,
151
+ CB_ACCESS_SIGN: api_signature
152
+ }
153
+
154
+ headers[:params] = params if params
155
+ headers[:ORDER_TYPE] = order_type if order_type
156
+ headers[:EVENT_NOTES] = event_notes if event_notes
157
+
158
+ response = rest_client_request.execute(
159
+ method: :DELETE,
160
+ url: "#{api_endpoint}#{api_call}",
161
+ headers: headers,
162
+ verify_ssl: false
163
+ )
164
+
165
+ when :POST
166
+ headers = {
167
+ content_type: 'application/json; charset=UTF-8',
168
+ CB_ACCESS_TIMESTAMP: api_timestamp,
169
+ CB_ACCESS_PASSPHRASE: api_passphrase,
170
+ CB_ACCESS_KEY: api_key,
171
+ CB_ACCESS_SIGN: api_signature
172
+ }
173
+
174
+ headers[:params] = params if params
175
+ headers[:ORDER_TYPE] = order_type if order_type
176
+ headers[:EVENT_NOTES] = event_notes if event_notes
177
+
178
+ response = rest_client_request.execute(
179
+ method: :POST,
180
+ url: "#{api_endpoint}#{api_call}",
181
+ headers: headers,
182
+ payload: http_body,
183
+ verify_ssl: false
184
+ )
185
+
186
+ else
187
+ raise @@logger.error("Unsupported HTTP Method #{http_method} for #{self} Plugin")
188
+ end
189
+
190
+ JSON.parse(response, symbolize_names: true)
191
+ rescue RestClient::Unauthorized => e
192
+ File.open('/tmp/cryptum-errors.txt', 'a') do |f|
193
+ f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
194
+ f.puts "#{self}\n#{e}\n\n\n"
195
+ end
196
+
197
+ raise e if conn_attempt > max_conn_attempts
198
+
199
+ sleep 60
200
+ retry
201
+ end
202
+ rescue RestClient::ExceptionWithResponse => e
203
+ File.open('/tmp/cryptum-errors.txt', 'a') do |f|
204
+ f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
205
+ f.puts "Module: #{self}"
206
+ f.puts "URL: #{api_endpoint}#{api_call}"
207
+ f.puts "PARAMS: #{params.inspect}"
208
+ f.puts "HTTP POST BODY: #{http_body.inspect}" if http_body != ''
209
+ f.puts "#{e}\n#{e.response}\n\n\n"
210
+ end
211
+
212
+ insufficient_funds = '{"message":"Insufficient funds"}'
213
+ size -= base_increment if e.response == insufficient_funds
214
+
215
+ sleep 0.3
216
+ retry
217
+ rescue RestClient::TooManyRequests => e
218
+ File.open('/tmp/cryptum-errors.txt', 'a') do |f|
219
+ f.puts Time.now.strftime('%Y-%m-%d %H:%M:%S.%N %z')
220
+ f.puts "Module: #{self}"
221
+ f.puts "URL: #{api_endpoint}#{api_call}"
222
+ f.puts "PARAMS: #{params.inspect}"
223
+ f.puts "HTTP POST BODY: #{http_body.inspect}" if http_body != ''
224
+ f.puts "#{e}\n#{e.response}\n\n\n"
225
+ end
226
+
227
+ sleep 1
228
+ retry
229
+ end
230
+
231
+ public_class_method def self.submit_limit_order(opts = {})
232
+ option_choice = opts[:option_choice]
233
+ env = opts[:env]
234
+ price = opts[:price]
235
+ size = opts[:size]
236
+ buy_or_sell = opts[:buy_or_sell]
237
+ event_history = opts[:event_history]
238
+ bot_conf = opts[:bot_conf]
239
+ buy_order_id = opts[:buy_order_id]
240
+
241
+ tpm = bot_conf[:target_profit_margin_percent].to_f
242
+ tpm_cast_as_decimal = tpm / 100
243
+
244
+ product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
245
+
246
+ this_product = event_history.order_book[:this_product]
247
+ base_increment = this_product[:base_increment]
248
+ quote_increment = this_product[:quote_increment]
249
+ # crypto_smallest_decimal = base_increment.to_s.split('.')[-1].length
250
+ fiat_smallest_decimal = quote_increment.to_s.split('.')[-1].length
251
+
252
+ order_hash = {}
253
+
254
+ order_hash[:type] = 'limit'
255
+ order_hash[:time_in_force] = 'GTC'
256
+
257
+ if buy_or_sell == :buy
258
+ order_hash[:time_in_force] = 'GTT'
259
+ order_hash[:cancel_after] = 'min'
260
+ end
261
+
262
+ order_hash[:size] = size
263
+ order_hash[:price] = price
264
+ order_hash[:side] = buy_or_sell
265
+ order_hash[:product_id] = product_id
266
+
267
+ http_body = order_hash.to_json
268
+
269
+ limit_order_resp = rest_api_call(
270
+ option_choice: option_choice,
271
+ env: env,
272
+ http_method: :POST,
273
+ api_call: '/orders',
274
+ http_body: http_body,
275
+ base_increment: base_increment
276
+ )
277
+
278
+ # Populate Order ID on the Buy
279
+ # to know what to do on the Sell
280
+ case buy_or_sell
281
+ when :buy
282
+ this_order = event_history.order_book[:order_plan].shift
283
+ this_order[:buy_order_id] = limit_order_resp[:id]
284
+ this_order[:price] = price
285
+ targ_price = price.to_f + (price.to_f * tpm_cast_as_decimal)
286
+ this_order[:tpm] = format(
287
+ '%0.2f',
288
+ tpm
289
+ )
290
+ this_order[:target_price] = format(
291
+ "%0.#{fiat_smallest_decimal}f",
292
+ targ_price
293
+ )
294
+
295
+ this_order[:size] = size
296
+
297
+ this_order[:color] = :cyan
298
+ event_history.order_book[:order_history_meta].push(this_order)
299
+ when :sell
300
+ sell_order_id = limit_order_resp[:id]
301
+ event_history.order_book[:order_history_meta].each do |meta|
302
+ if meta[:buy_order_id] == buy_order_id
303
+ meta[:sell_order_id] = sell_order_id
304
+ meta[:color] = :yellow
305
+ end
306
+ end
307
+ end
308
+
309
+ event_history
310
+ rescue StandardError => e
311
+ raise e
312
+ end
313
+
314
+ public_class_method def self.gtfo(opts = {})
315
+ option_choice = opts[:option_choice]
316
+ env = opts[:env]
317
+ event_history = opts[:event_history]
318
+
319
+ # Cancel all open orders
320
+ cancel_all_open_orders(
321
+ env: env,
322
+ option_choice: option_choice
323
+ )
324
+
325
+ product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
326
+
327
+ this_product = event_history.order_book[:this_product]
328
+ base_increment = this_product[:base_increment]
329
+ quote_increment = this_product[:quote_increment]
330
+ crypto_smallest_decimal = base_increment.to_s.split('.')[-1].length
331
+ fiat_smallest_decimal = quote_increment.to_s.split('.')[-1].length
332
+
333
+ # TODO: Calculate / Price / Size
334
+ last_three_prices_arr = []
335
+ last_ticker_price = event_history.order_book[:ticker_price].to_f
336
+ second_to_last_ticker_price = event_history.order_book[:ticker_price_second_to_last].to_f
337
+ third_to_last_ticker_price = event_history.order_book[:ticker_price_third_to_last].to_f
338
+ last_three_prices_arr.push(last_ticker_price)
339
+ last_three_prices_arr.push(second_to_last_ticker_price)
340
+ last_three_prices_arr.push(third_to_last_ticker_price)
341
+ limit_price = last_three_prices_arr.sort[1]
342
+ price = format(
343
+ "%0.#{fiat_smallest_decimal}f",
344
+ limit_price
345
+ )
346
+
347
+ crypto_currency = option_choice.symbol.to_s.upcase.split('_').first.to_sym
348
+ portfolio = event_history.order_book[:portfolio]
349
+ this_account = portfolio.select do |account|
350
+ account[:currency] == crypto_currency.to_s
351
+ end
352
+ balance = format(
353
+ "%0.#{crypto_smallest_decimal}f",
354
+ this_account.first[:balance]
355
+ )
356
+
357
+ current_crypto_fiat_value = format(
358
+ '%0.2f',
359
+ balance.to_f * price.to_f
360
+ )
361
+
362
+ order_hash = {}
363
+
364
+ order_hash[:type] = 'limit'
365
+ order_hash[:time_in_force] = 'GTT'
366
+ order_hash[:cancel_after] = 'min'
367
+
368
+ order_hash[:size] = balance
369
+ order_hash[:price] = price
370
+ order_hash[:side] = :sell
371
+ order_hash[:product_id] = product_id
372
+
373
+ http_body = order_hash.to_json
374
+
375
+ limit_order_resp = rest_api_call(
376
+ option_choice: option_choice,
377
+ env: env,
378
+ http_method: :POST,
379
+ api_call: '/orders',
380
+ http_body: http_body,
381
+ base_increment: base_increment
382
+ )
383
+
384
+ # Populate Order ID on the Buy
385
+ # to know what to do on the Sell
386
+ this_order = {}
387
+ this_order[:plan_no] = '0.0'
388
+ this_order[:fiat_available] = '0.00'
389
+ this_order[:risk_alloc] = current_crypto_fiat_value
390
+ this_order[:allocation_decimal] = '1.0'
391
+ this_order[:allocation_percent] = '100.0'
392
+ this_order[:invest] = current_crypto_fiat_value
393
+ this_order[:return] = current_crypto_fiat_value
394
+ this_order[:profit] = '0.0'
395
+ this_order[:buy_order_id] = 'N/A'
396
+ this_order[:price] = price
397
+ this_order[:tpm] = '0.00'
398
+ this_order[:target_price] = current_crypto_fiat_value
399
+ this_order[:size] = balance
400
+ this_order[:color] = :magenta
401
+ this_order[:sell_order_id] = limit_order_resp[:id]
402
+
403
+ event_history.order_book[:order_history_meta].push(this_order)
404
+
405
+ event_history
406
+ rescue StandardError => e
407
+ raise e
408
+ end
409
+
410
+ # public_class_method def self.cancel_open_order(opts = {})
411
+ # env = opts[:env]
412
+ # option_choice = opts[:option_choice]
413
+ # order_id = opts[:order_id]
414
+ # order_type = opts[:order_type]
415
+
416
+ # product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
417
+
418
+ # order_hash = {}
419
+ # order_hash[:product_id] = product_id
420
+
421
+ # params = order_hash
422
+
423
+ # rest_api_call(
424
+ # env: env,
425
+ # http_method: :DELETE,
426
+ # api_call: "/orders/#{order_id}",
427
+ # option_choice: option_choice,
428
+ # params: params,
429
+ # order_type: order_type
430
+ # )
431
+ # rescue StandardError => e
432
+ # raise e
433
+ # end
434
+
435
+ public_class_method def self.cancel_all_open_orders(opts = {})
436
+ env = opts[:env]
437
+ option_choice = opts[:option_choice]
438
+ event_notes = opts[:event_notes]
439
+
440
+ product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
441
+
442
+ order_hash = {}
443
+ order_hash[:product_id] = product_id
444
+
445
+ # http_body = order_hash.to_json
446
+ params = order_hash
447
+
448
+ canceled_order_id_arr = []
449
+ loop do
450
+ canceled_order_id_arr = rest_api_call(
451
+ env: env,
452
+ http_method: :DELETE,
453
+ api_call: '/orders',
454
+ option_choice: option_choice,
455
+ params: params,
456
+ event_notes: event_notes
457
+ )
458
+
459
+ break if canceled_order_id_arr.empty?
460
+ end
461
+ canceled_order_id_arr
462
+ rescue StandardError => e
463
+ raise e
464
+ end
465
+
466
+ public_class_method def self.get_products(opts = {})
467
+ option_choice = opts[:option_choice]
468
+ env = opts[:env]
469
+
470
+ products = rest_api_call(
471
+ option_choice: option_choice,
472
+ env: env,
473
+ http_method: :GET,
474
+ api_call: '/products'
475
+ )
476
+
477
+ if products.length.positive?
478
+ supported_products_filter = products.select do |product|
479
+ product[:id].match?(/USD$/) &&
480
+ product[:status] == 'online' &&
481
+ product[:fx_stablecoin] == false
482
+ end
483
+ sorted_products = supported_products_filter.sort_by { |hash| hash[:id] }
484
+ end
485
+
486
+ sorted_products
487
+ rescue StandardError => e
488
+ raise e
489
+ end
490
+
491
+ private_class_method def self.get_exchange_rates(opts = {})
492
+ option_choice = opts[:option_choice]
493
+ env = opts[:env]
494
+
495
+ api_endpoint = 'https://api.coinbase.com/v2'
496
+ exchange_rates_api_call = '/exchange-rates'
497
+
498
+ # We don't always get fees back from Coinbase...
499
+ # This is a hack to ensure we do.
500
+ exchange = {}
501
+ exchange = rest_api_call(
502
+ option_choice: option_choice,
503
+ env: env,
504
+ http_method: :GET,
505
+ api_endpoint: api_endpoint,
506
+ api_call: exchange_rates_api_call
507
+ )
508
+
509
+ exchange[:data][:rates]
510
+ rescue StandardError => e
511
+ raise e
512
+ end
513
+
514
+ public_class_method def self.get_portfolio(opts = {})
515
+ option_choice = opts[:option_choice]
516
+ env = opts[:env]
517
+ crypto = opts[:crypto]
518
+ fiat = opts[:fiat]
519
+ fiat_portfolio_file = opts[:fiat_portfolio_file]
520
+ event_notes = opts[:event_notes]
521
+
522
+ # Retrieve Exchange Rates
523
+ exchange_rates = get_exchange_rates(
524
+ option_choice: option_choice,
525
+ env: env
526
+ )
527
+
528
+ portfolio_complete_arr = []
529
+ portfolio_complete_arr = rest_api_call(
530
+ option_choice: option_choice,
531
+ env: env,
532
+ http_method: :GET,
533
+ api_call: '/accounts',
534
+ event_notes: event_notes
535
+ )
536
+
537
+ all_products = portfolio_complete_arr.select do |products|
538
+ products if products[:balance].to_f.positive?
539
+ end
540
+
541
+ total_holdings = 0.00
542
+ all_products.each do |product|
543
+ currency = product[:currency].to_sym
544
+ this_exchange_rate = exchange_rates[currency].to_f
545
+ total_holdings += product[:balance].to_f / this_exchange_rate
546
+ end
547
+
548
+ crypto_portfolio = portfolio_complete_arr.select do |product|
549
+ product if product[:currency] == crypto
550
+ end
551
+
552
+ fiat_portfolio = portfolio_complete_arr.select do |product|
553
+ product if product[:currency] == fiat
554
+ end
555
+ fiat_portfolio.last[:total_holdings] = format(
556
+ '%0.8f',
557
+ total_holdings
558
+ )
559
+
560
+ File.write(
561
+ fiat_portfolio_file,
562
+ JSON.pretty_generate(fiat_portfolio)
563
+ )
564
+
565
+ crypto_portfolio
566
+ rescue StandardError => e
567
+ raise e
568
+ end
569
+
570
+ public_class_method def self.get_fees(opts = {})
571
+ option_choice = opts[:option_choice]
572
+ env = opts[:env]
573
+
574
+ fees_api_call = '/fees'
575
+
576
+ # We don't always get fees back from Coinbase...
577
+ # This is a hack to ensure we do.
578
+ fees = {}
579
+ fees = rest_api_call(
580
+ option_choice: option_choice,
581
+ env: env,
582
+ http_method: :GET,
583
+ api_call: fees_api_call
584
+ )
585
+
586
+ fees
587
+ rescue StandardError => e
588
+ raise e
589
+ end
590
+
591
+ # public_class_method def self.get_profiles(opts = {})
592
+ # option_choice = opts[:option_choice]
593
+ # env = opts[:env]
594
+
595
+ # profiles_api_call = '/profiles'
596
+
597
+ # # We don't always get fees back from Coinbase...
598
+ # # This is a hack to ensure we do.
599
+ # profiles = {}
600
+ # # loop do
601
+ # profiles = rest_api_call(
602
+ # option_choice: option_choice,
603
+ # env: env,
604
+ # http_method: :GET,
605
+ # api_call: profiles_api_call
606
+ # )
607
+
608
+ # # break unless fees.empty?
609
+
610
+ # # sleep 0.3
611
+ # # end
612
+
613
+ # profiles
614
+ # rescue StandardError => e
615
+ # raise e
616
+ # end
617
+
618
+ public_class_method def self.get_order_history(opts = {})
619
+ option_choice = opts[:option_choice]
620
+ env = opts[:env]
621
+
622
+ product_id = option_choice.symbol.to_s.gsub('_', '-').upcase
623
+
624
+ orders_api_call = '/orders'
625
+ params = {}
626
+ params[:product_id] = product_id
627
+ params[:status] = 'all'
628
+
629
+ # We don't always get order_history back from Coinbase...
630
+ # This is a hack to ensure we do.
631
+ order_history = []
632
+ order_history = rest_api_call(
633
+ option_choice: option_choice,
634
+ env: env,
635
+ http_method: :GET,
636
+ api_call: orders_api_call,
637
+ params: params
638
+ )
639
+
640
+ # Cast UTC Timestamps as local times
641
+ order_history.each do |order|
642
+ order[:created_at] = Time.parse(
643
+ order[:created_at]
644
+ ).localtime.to_s
645
+
646
+ next unless order[:done_at]
647
+
648
+ order[:done_at] = Time.parse(
649
+ order[:done_at]
650
+ ).localtime.to_s
651
+ end
652
+
653
+ order_history
654
+ rescue StandardError => e
655
+ raise e
656
+ end
657
+
658
+ # Display Usage for this Module
659
+
660
+ public_class_method def self.help
661
+ puts "USAGE:
662
+ signature = #{self}.generate_signature(
663
+ api_secret: 'required - Coinbase Pro API Secret',
664
+ http_method: 'optional - Defaults to :GET',
665
+ api_call: 'optional - Defaults to /users/self/verify',
666
+ params: 'optional - HTTP GET Parameters',
667
+ http_body: 'optional HTTP POST Body'
668
+ )
669
+
670
+ profiles = #{self}.get_profiles(
671
+ env: 'required - Coinbase::Option.get_env Object'
672
+ )
673
+
674
+ products = #{self}.get_products(
675
+ env: 'required - Coinbase::Option.get_env Object'
676
+ )
677
+
678
+ portfolio = #{self}.get_portfolio(
679
+ env: 'required - Coinbase::Option.get_env Object'
680
+ )
681
+
682
+ order_history = #{self}.get_order_history(
683
+ env: 'required - Coinbase::Option.get_env Object'
684
+ )
685
+ "
686
+ end
687
+ end
688
+ end