gera 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +67 -0
  4. data/Rakefile +43 -0
  5. data/app/assets/config/gera_manifest.js +2 -0
  6. data/app/assets/javascripts/gera/application.js +14 -0
  7. data/app/assets/stylesheets/gera/application.css +15 -0
  8. data/app/authorizers/gera/application_authorizer.rb +6 -0
  9. data/app/authorizers/gera/currency_rate_authorizer.rb +9 -0
  10. data/app/authorizers/gera/currency_rate_mode_authorizer.rb +7 -0
  11. data/app/authorizers/gera/direction_rate_authorizer.rb +8 -0
  12. data/app/authorizers/gera/exchange_rate_authorizer.rb +8 -0
  13. data/app/authorizers/gera/payment_system_authorizer.rb +13 -0
  14. data/app/authorizers/gera/rate_source_authorizer.rb +7 -0
  15. data/app/controllers/gera/application_controller.rb +30 -0
  16. data/app/controllers/gera/currencies_controller.rb +8 -0
  17. data/app/controllers/gera/currency_rate_history_intervals_controller.rb +53 -0
  18. data/app/controllers/gera/currency_rate_mode_snapshots_controller.rb +83 -0
  19. data/app/controllers/gera/currency_rate_modes_controller.rb +60 -0
  20. data/app/controllers/gera/currency_rates_controller.rb +70 -0
  21. data/app/controllers/gera/direction_rate_history_intervals_controller.rb +62 -0
  22. data/app/controllers/gera/direction_rates_controller.rb +28 -0
  23. data/app/controllers/gera/exchange_rates_controller.rb +25 -0
  24. data/app/controllers/gera/external_rate_snapshots_controller.rb +35 -0
  25. data/app/controllers/gera/external_rates_controller.rb +28 -0
  26. data/app/controllers/gera/payment_systems_controller.rb +78 -0
  27. data/app/controllers/gera/rate_sources_controller.rb +8 -0
  28. data/app/decorators/gera/currency_rate_decorator.rb +148 -0
  29. data/app/decorators/gera/direction_rate_decorator.rb +47 -0
  30. data/app/decorators/gera/payment_system_decorator.rb +19 -0
  31. data/app/helpers/gera/application_helper.rb +110 -0
  32. data/app/helpers/gera/currency_rate_helper.rb +19 -0
  33. data/app/helpers/gera/currency_rate_modes_helper.rb +73 -0
  34. data/app/helpers/gera/direction_rate_helper.rb +36 -0
  35. data/app/helpers/gera/exchange_rates_helper.rb +21 -0
  36. data/app/jobs/gera/application_job.rb +6 -0
  37. data/app/models/concerns/gera/currency_pair_generator.rb +14 -0
  38. data/app/models/concerns/gera/currency_pair_support.rb +42 -0
  39. data/app/models/concerns/gera/currency_rate_mode_builder_support.rb +35 -0
  40. data/app/models/concerns/gera/direction_support.rb +14 -0
  41. data/app/models/concerns/gera/history_interval_concern.rb +46 -0
  42. data/app/models/gera/application_record.rb +8 -0
  43. data/app/models/gera/cbr_external_rate.rb +13 -0
  44. data/app/models/gera/cross_rate_mode.rb +13 -0
  45. data/app/models/gera/currency_rate.rb +78 -0
  46. data/app/models/gera/currency_rate_history_interval.rb +25 -0
  47. data/app/models/gera/currency_rate_history_interval_filter.rb +30 -0
  48. data/app/models/gera/currency_rate_mode.rb +20 -0
  49. data/app/models/gera/currency_rate_mode_snapshot.rb +26 -0
  50. data/app/models/gera/currency_rate_snapshot.rb +12 -0
  51. data/app/models/gera/direction.rb +44 -0
  52. data/app/models/gera/direction_rate.rb +136 -0
  53. data/app/models/gera/direction_rate_history_interval.rb +33 -0
  54. data/app/models/gera/direction_rate_history_interval_filter.rb +30 -0
  55. data/app/models/gera/direction_rate_snapshot.rb +7 -0
  56. data/app/models/gera/exchange_rate.rb +130 -0
  57. data/app/models/gera/external_rate.rb +38 -0
  58. data/app/models/gera/external_rate_snapshot.rb +20 -0
  59. data/app/models/gera/payment_system.rb +98 -0
  60. data/app/models/gera/rate_source.rb +77 -0
  61. data/app/models/gera/rate_source_auto.rb +25 -0
  62. data/app/models/gera/rate_source_bitfinex.rb +9 -0
  63. data/app/models/gera/rate_source_cbr.rb +13 -0
  64. data/app/models/gera/rate_source_cbr_avg.rb +6 -0
  65. data/app/models/gera/rate_source_exmo.rb +9 -0
  66. data/app/models/gera/rate_source_manual.rb +13 -0
  67. data/app/views/application/_exchange_rate_monitor.slim +1 -0
  68. data/app/views/gera/application/_attributes_table.slim +5 -0
  69. data/app/views/gera/application/_currencies_table.slim +18 -0
  70. data/app/views/gera/application/_currency_rate_mode_snapshots.slim +6 -0
  71. data/app/views/gera/application/_direction_rate.slim +6 -0
  72. data/app/views/gera/application/_directions_submenu.slim +11 -0
  73. data/app/views/gera/application/_directions_table.slim +15 -0
  74. data/app/views/gera/application/_exchange_rate.slim +6 -0
  75. data/app/views/gera/application/_external_rate.slim +6 -0
  76. data/app/views/gera/application/_header.slim +12 -0
  77. data/app/views/gera/application/_table.slim +28 -0
  78. data/app/views/gera/application/_topnav.slim +21 -0
  79. data/app/views/gera/application/direction_details.slim +75 -0
  80. data/app/views/gera/application/page.html.slim +1 -0
  81. data/app/views/gera/currencies/index.html.slim +20 -0
  82. data/app/views/gera/currency_rate_history_intervals/_filter.slim +11 -0
  83. data/app/views/gera/currency_rate_history_intervals/index.slim +39 -0
  84. data/app/views/gera/currency_rate_mode_snapshots/_snapshot.slim +20 -0
  85. data/app/views/gera/currency_rate_mode_snapshots/edit.html.slim +30 -0
  86. data/app/views/gera/currency_rate_modes/_cross_rate_mode_fields.slim +21 -0
  87. data/app/views/gera/currency_rate_modes/_currency_rate_mode.slim +7 -0
  88. data/app/views/gera/currency_rate_modes/_form.slim +29 -0
  89. data/app/views/gera/currency_rate_modes/edit.slim +11 -0
  90. data/app/views/gera/currency_rate_modes/index.html.slim +12 -0
  91. data/app/views/gera/currency_rates/_currency_rate.slim +22 -0
  92. data/app/views/gera/currency_rates/_currency_rate_humanized.slim +35 -0
  93. data/app/views/gera/currency_rates/diff.slim +12 -0
  94. data/app/views/gera/currency_rates/index.slim +27 -0
  95. data/app/views/gera/currency_rates/modes.slim +14 -0
  96. data/app/views/gera/currency_rates/show.slim +4 -0
  97. data/app/views/gera/direction_rate_history_intervals/_filter.slim +11 -0
  98. data/app/views/gera/direction_rate_history_intervals/index.slim +41 -0
  99. data/app/views/gera/direction_rates/_direction_rate_example_calculator.slim +28 -0
  100. data/app/views/gera/direction_rates/diff.slim +19 -0
  101. data/app/views/gera/direction_rates/index.slim +26 -0
  102. data/app/views/gera/direction_rates/legacy.html.slim +5 -0
  103. data/app/views/gera/direction_rates/minimals.slim +5 -0
  104. data/app/views/gera/direction_rates/show.slim +14 -0
  105. data/app/views/gera/external_rate_snapshots/index.slim +25 -0
  106. data/app/views/gera/external_rate_snapshots/show.slim +22 -0
  107. data/app/views/gera/external_rates/_flat_list.slim +22 -0
  108. data/app/views/gera/external_rates/index.html.slim +28 -0
  109. data/app/views/gera/external_rates/show.slim +1 -0
  110. data/app/views/gera/payment_systems/_fields.slim +4 -0
  111. data/app/views/gera/payment_systems/_fields_table.slim +18 -0
  112. data/app/views/gera/payment_systems/_form.slim +9 -0
  113. data/app/views/gera/payment_systems/edit.slim +7 -0
  114. data/app/views/gera/payment_systems/index.slim +3 -0
  115. data/app/views/gera/payment_systems/new.slim +9 -0
  116. data/app/views/gera/payment_systems/show.slim +1 -0
  117. data/app/views/gera/rate_sources/_list.slim +13 -0
  118. data/app/views/gera/rate_sources/_rate_source.slim +23 -0
  119. data/app/views/gera/rate_sources/index.html.slim +6 -0
  120. data/app/views/layouts/application.html.slim +15 -0
  121. data/app/views/layouts/gera/blank.html.slim +10 -0
  122. data/app/workers/concerns/gera/rates_worker.rb +87 -0
  123. data/app/workers/gera/bitfinex_rates_worker.rb +65 -0
  124. data/app/workers/gera/cbr_avg_rates_worker.rb +42 -0
  125. data/app/workers/gera/cbr_rates_worker.rb +192 -0
  126. data/app/workers/gera/create_history_intervals_worker.rb +38 -0
  127. data/app/workers/gera/currency_rates_worker.rb +64 -0
  128. data/app/workers/gera/directions_rates_worker.rb +51 -0
  129. data/app/workers/gera/exmo_rates_worker.rb +62 -0
  130. data/app/workers/gera/purge_currency_rates_worker.rb +21 -0
  131. data/app/workers/gera/purge_direction_rates_worker.rb +30 -0
  132. data/config/currencies.yml +559 -0
  133. data/config/routes.rb +30 -0
  134. data/db/migrate/20180912130000_setup.rb +228 -0
  135. data/db/migrate/20190314114844_add_is_available_to_payment_systems.rb +7 -0
  136. data/db/migrate/20190315113046_add_icon_url_to_payment_systems.rb +7 -0
  137. data/db/migrate/20190315132137_add_commission_to_payment_systems.rb +7 -0
  138. data/lib/banks/currency_exchange.rb +12 -0
  139. data/lib/builders/currency_rate_auto_builder.rb +38 -0
  140. data/lib/builders/currency_rate_builder.rb +60 -0
  141. data/lib/builders/currency_rate_cross_builder.rb +70 -0
  142. data/lib/builders/currency_rate_direct_builder.rb +26 -0
  143. data/lib/gera.rb +32 -0
  144. data/lib/gera/bitfinex_fetcher.rb +36 -0
  145. data/lib/gera/configuration.rb +47 -0
  146. data/lib/gera/currencies_purger.rb +28 -0
  147. data/lib/gera/currency_pair.rb +93 -0
  148. data/lib/gera/engine.rb +14 -0
  149. data/lib/gera/mathematic.rb +156 -0
  150. data/lib/gera/money_support.rb +89 -0
  151. data/lib/gera/numeric.rb +25 -0
  152. data/lib/gera/railtie.rb +8 -0
  153. data/lib/gera/rate.rb +22 -0
  154. data/lib/gera/rate_from_multiplicator.rb +54 -0
  155. data/lib/gera/repositories/currency_rate_modes_repository.rb +37 -0
  156. data/lib/gera/repositories/currency_rates_repository.rb +25 -0
  157. data/lib/gera/repositories/direction_rates_repository.rb +46 -0
  158. data/lib/gera/repositories/exchange_rates_repository.rb +23 -0
  159. data/lib/gera/repositories/payment_systems_repository.rb +21 -0
  160. data/lib/gera/repositories/universe.rb +47 -0
  161. data/lib/gera/version.rb +3 -0
  162. data/lib/tasks/auto_generate_diagram.rake +6 -0
  163. data/lib/tasks/gera_tasks.rake +4 -0
  164. metadata +765 -0
@@ -0,0 +1 @@
1
+ = render 'external_rate', external_rate: external_rate
@@ -0,0 +1,4 @@
1
+ h2 Поля в заявках
2
+
3
+ = render 'fields_table', title: 'Входящие поля', direction: :income, scope: payment_system.income_form_fields, payment_system: payment_system
4
+ = render 'fields_table', title: 'Иходящие поля', direction: :outcome, scope: payment_system.outcome_form_fields, payment_system: payment_system
@@ -0,0 +1,18 @@
1
+ - columns = %i(key i18n_key title checkbox_title is_required field_type actions)
2
+ h3= title
3
+
4
+ = render 'table',
5
+ scope: scope,
6
+ decorator_class: PaymentSystemFormFieldDecorator,
7
+ parent: payment_system,
8
+ columns: columns,
9
+ allow_edit: true
10
+
11
+ - if current_user.has_permission?(payment_system, :update)
12
+ - if unused_payment_system_form_fields_collection(payment_system, direction).any?
13
+ = link_to 'Добавить поле',
14
+ new_payment_system_payment_system_form_field_path(payment_system, payment_system_form_field: { direction: direction }),
15
+ class: 'btn btn-sm btn-success'
16
+ - else
17
+ .alert.alert-info Поля всех видов добавлены
18
+ hr
@@ -0,0 +1,9 @@
1
+ = simple_form_for payment_system, wrapper_mappings: { boolean: :vertical_boolean } do |f|
2
+ .row
3
+ - columns.each do |column|
4
+ .col-md-3
5
+ = smart_input f, column
6
+ hr
7
+ .row
8
+ .col-md-12
9
+ = f.button :submit, class: 'btn btn-lg btn-primary'
@@ -0,0 +1,7 @@
1
+ = link_to '← назад в список платежных систем'.html_safe, payment_systems_path
2
+ .page-header
3
+ h2 Редактируем платежную систему #{ps_icon payment_system} ##{payment_system.id}
4
+
5
+ .row
6
+ .col-md-9
7
+ = render 'form', payment_system: payment_system, columns: columns
@@ -0,0 +1,3 @@
1
+ = render 'table', scope: payment_systems, decorator_class: Gera.payment_system_decorator, columns: columns, allow_edit: true
2
+ hr
3
+ = link_to 'Добавить новую ПС', new_payment_system_path, class: 'btn btn-success' if current_user.has_permission?(Gera::PaymentSystem, :create)
@@ -0,0 +1,9 @@
1
+ = link_to '← назад в список платежных систем'.html_safe, payment_systems_path
2
+ .page-header
3
+ h2 Создаем новую платежную ситсему
4
+
5
+ .row
6
+ .col-md-6
7
+ = render 'form', payment_system: payment_system, columns: columns
8
+ .col-md-6
9
+ .alert.alert-info Сохраните форму, чтобы иметь возможность редактировать банки карт заршеренные к приему
@@ -0,0 +1 @@
1
+ = render 'attributes_table', object: Gera.payment_system_decorator.decorate(payment_system), columns: columns
@@ -0,0 +1,13 @@
1
+ table.table.table-condensed.table-hover.table-striped.table-narrow.text-small
2
+ thead
3
+ tr
4
+ th.text-right Принимаем
5
+ th.text-right Отдаем
6
+ th.text-right Актуальность
7
+ tbody
8
+ - external_rates.each do |er|
9
+ tr
10
+ td.text-right= er.currency_pair.first
11
+ td.text-right= rate_cell_with_currency er.rate_value, er.currency_to
12
+ td.text-right.text-muted= l er.created_at, format: :long
13
+
@@ -0,0 +1,23 @@
1
+ h4
2
+ = source
3
+ span.m-l-md.text-small.text-muted= link_to 'Журнал курсов', external_rate_snapshots_path(rate_source_id: source.id)
4
+
5
+ - snapshot = source.actual_snapshot
6
+
7
+ p Дата создания #{l source.updated_at, format: :long}
8
+
9
+ - if snapshot.present?
10
+ -# = render 'list', external_rates: snapshot.external_rates
11
+ = render layout: 'currencies_table', locals: { currencies: source.supported_currencies } do |pair|
12
+ td
13
+ - if pair.same?
14
+ .text-muted= '1'
15
+ - else
16
+ - er = snapshot.external_rates.find_by_currency_pair(pair)
17
+ - if er.present?
18
+ = humanized_rate er.rate_value
19
+ - else
20
+ .text-muted= middot
21
+
22
+ - else
23
+ .alert.alert-warning Нет актуальных данных
@@ -0,0 +1,6 @@
1
+ .pull-right
2
+ ul.nav
3
+ li= link_to 'Базовый курс', currency_rates_path
4
+
5
+ - Gera::RateSource.ordered.each do |source|
6
+ = render 'rate_source', source: source
@@ -0,0 +1,15 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title= app_title
5
+ = csrf_meta_tags
6
+ = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
7
+ = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
8
+ body
9
+ = render 'header'
10
+
11
+ .container-fluid.container-margin-bottom style='margin-top: 80px'
12
+ - if breadcrumbs.nil?
13
+ ol.breadcrumb= render_breadcrumbs tag: :li, separator: '', class: '', item_class: '', divider_class: '', active_class: 'active'
14
+ = yield
15
+ = noty_flashes
@@ -0,0 +1,10 @@
1
+ doctype html
2
+ html
3
+ head
4
+ title= app_title
5
+ = csrf_meta_tags
6
+ = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
7
+ = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
8
+ = javascript_include_tag 'bestchange_widget', 'data-turbolinks-track': 'reload'
9
+ body
10
+ = yield
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+ require 'rest-client'
5
+
6
+ module Gera
7
+ # Import rates from all sources
8
+ #
9
+ module RatesWorker
10
+ Error = Class.new StandardError
11
+
12
+ def perform
13
+ # Alternative approach is `Model.uncached do`
14
+ ActiveRecord::Base.connection.clear_query_cache
15
+
16
+ rates # Load before a translaction
17
+
18
+ rate_source.with_lock do
19
+ create_snapshot
20
+ rates.each do |pair, data|
21
+ save_rate pair, data
22
+ end
23
+ rate_source.update actual_snapshot_id: snapshot.id
24
+ end
25
+
26
+ CurrencyRatesWorker.new.perform
27
+
28
+ snapshot.id
29
+
30
+ # EXMORatesWorker::Error: Error 40016: Maintenance work in progress
31
+ rescue ActiveRecord::RecordNotUnique, RestClient::TooManyRequests => error
32
+ raise error if Rails.env.test?
33
+
34
+ logger.error error
35
+ Bugsnag.notify error do |b|
36
+ b.severity = :warning
37
+ b.meta_data = { error: error }
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :snapshot
44
+
45
+ delegate :actual_for, to: :snapshot
46
+
47
+ def create_snapshot
48
+ @snapshot ||= rate_source.snapshots.create! actual_for: Time.zone.now
49
+ end
50
+
51
+ def rates
52
+ @rates ||= load_rates
53
+ end
54
+
55
+ def create_external_rates(currency_pair, data, sell_price:, buy_price:)
56
+ logger.warn "Ignore #{currency_pair}" unless CurrencyPair.all.include? currency_pair
57
+
58
+ logger.info "save_rate_for_date #{actual_for}, #{currency_pair} #{data}"
59
+ ExternalRate.create!(
60
+ currency_pair: currency_pair,
61
+ snapshot: snapshot,
62
+ source: rate_source,
63
+ rate_value: buy_price.to_f
64
+ )
65
+ ExternalRate.create!(
66
+ currency_pair: currency_pair.inverse,
67
+ snapshot: snapshot,
68
+ source: rate_source,
69
+ rate_value: 1.0 / sell_price.to_f
70
+ )
71
+ rescue ActiveRecord::RecordNotUnique => err
72
+ raise error if Rails.env.test?
73
+
74
+ if err.message.include? 'external_rates_unique_index'
75
+ logger.debug "save_rate_for_date: #{actual_for} , #{currency_pair} -> #{err}"
76
+ if defined? Bugsnag
77
+ Bugsnag.notify 'Try to rewrite rates' do |b|
78
+ b.meta_data = { actual_for: actual_for, snapshot_id: snapshot.id, currency_pair: currency_pair }
79
+ end
80
+ end
81
+ else
82
+ logger.error "save_rate_for_date: #{actual_for} , #{pair} -> #{err}"
83
+ raise error
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ # Import rates from Bitfinex
5
+ #
6
+ class BitfinexRatesWorker
7
+ include Sidekiq::Worker
8
+ include AutoLogger
9
+
10
+ prepend RatesWorker
11
+
12
+ # Stolen from: https://api.bitfinex.com/v1/symbols
13
+ AVAILABLE_TICKETS = %i[btcusd ltcusd ltcbtc ethusd ethbtc etcbtc etcusd rrtusd rrtbtc zecusd zecbtc xmrusd xmrbtc dshusd dshbtc btceur btcjpy xrpusd
14
+ xrpbtc iotusd iotbtc ioteth eosusd eosbtc eoseth sanusd sanbtc saneth omgusd omgbtc omgeth neousd neobtc neoeth etpusd etpbtc
15
+ etpeth qtmusd qtmbtc qtmeth avtusd avtbtc avteth edousd edobtc edoeth btgusd btgbtc datusd datbtc dateth qshusd qshbtc qsheth
16
+ yywusd yywbtc yyweth gntusd gntbtc gnteth sntusd sntbtc snteth ioteur batusd batbtc bateth mnausd mnabtc mnaeth funusd funbtc
17
+ funeth zrxusd zrxbtc zrxeth tnbusd tnbbtc tnbeth spkusd spkbtc spketh trxusd trxbtc trxeth rcnusd rcnbtc rcneth rlcusd rlcbtc
18
+ rlceth aidusd aidbtc aideth sngusd sngbtc sngeth repusd repbtc repeth elfusd elfbtc elfeth btcgbp etheur ethjpy ethgbp neoeur
19
+ neojpy neogbp eoseur eosjpy eosgbp iotjpy iotgbp iosusd iosbtc ioseth aiousd aiobtc aioeth requsd reqbtc reqeth rdnusd rdnbtc
20
+ rdneth lrcusd lrcbtc lrceth waxusd waxbtc waxeth daiusd daibtc daieth agiusd agibtc agieth bftusd bftbtc bfteth mtnusd mtnbtc
21
+ mtneth odeusd odebtc odeeth antusd antbtc anteth dthusd dthbtc dtheth mitusd mitbtc miteth stjusd stjbtc stjeth xlmusd xlmeur
22
+ xlmjpy xlmgbp xlmbtc xlmeth xvgusd xvgeur xvgjpy xvggbp xvgbtc xvgeth bciusd bcibtc mkrusd mkrbtc mkreth kncusd kncbtc knceth
23
+ poausd poabtc poaeth lymusd lymbtc lymeth utkusd utkbtc utketh veeusd veebtc veeeth dadusd dadbtc dadeth orsusd orsbtc orseth
24
+ aucusd aucbtc auceth poyusd poybtc poyeth fsnusd fsnbtc fsneth cbtusd cbtbtc cbteth zcnusd zcnbtc zcneth senusd senbtc seneth
25
+ ncausd ncabtc ncaeth cndusd cndbtc cndeth ctxusd ctxbtc ctxeth paiusd paibtc seeusd seebtc seeeth essusd essbtc esseth atmusd
26
+ atmbtc atmeth hotusd hotbtc hoteth dtausd dtabtc dtaeth iqxusd iqxbtc iqxeos wprusd wprbtc wpreth zilusd zilbtc zileth bntusd
27
+ bntbtc bnteth absusd abseth xrausd xraeth manusd maneth bbnusd bbneth niousd nioeth dgxusd dgxeth vetusd vetbtc veteth utnusd
28
+ utneth tknusd tkneth gotusd goteur goteth xtzusd xtzbtc cnnusd cnneth boxusd boxeth trxeur trxgbp trxjpy mgousd mgoeth rteusd
29
+ rteeth yggusd yggeth mlnusd mlneth wtcusd wtceth csxusd csxeth omnusd omnbtc intusd inteth drnusd drneth pnkusd pnketh dgbusd
30
+ dgbbtc bsvusd bsvbtc babusd babbtc wlousd wloxlm vldusd vldeth enjusd enjeth onlusd onleth rbtusd rbtbtc ustusd euteur eutusd
31
+ gsdusd udcusd tsdusd paxusd rifusd rifbtc pasusd paseth vsyusd vsybtc zrxdai mkrdai omgdai bttusd bttbtc btcust ethust clousd
32
+ clobtc].freeze
33
+
34
+ # NOTE: formar tickers neousd neobtc neoeth neoeur
35
+ TICKERS = %i[].freeze
36
+
37
+ private
38
+
39
+ def rate_source
40
+ @rate_source ||= RateSourceBitfinex.get!
41
+ end
42
+
43
+ # {"mid":"8228.25",
44
+ # "bid":"8228.2",
45
+ # "ask":"8228.3",
46
+ # "last_price":"8228.3",
47
+ # "low":"8055.0",
48
+ # "high":"8313.3",
49
+ # "volume":"13611.826947359996",
50
+ # "timestamp":"1532874580.9087598"}
51
+ def save_rate(ticker, data)
52
+ currency_pair = pair_from_ticker ticker
53
+ create_external_rates currency_pair, data, sell_price: data['high'], buy_price: data['low']
54
+ end
55
+
56
+ def pair_from_ticker(ticker)
57
+ ticker = ticker.to_s
58
+ CurrencyPair.new ticker[0, 3], ticker[3, 3]
59
+ end
60
+
61
+ def load_rates
62
+ TICKERS.each_with_object({}) { |ticker, ag| ag[ticker] = BitfinexFetcher.new(ticker: ticker).perform }
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gera
4
+ class CBRAvgRatesWorker
5
+ include Sidekiq::Worker
6
+ include AutoLogger
7
+
8
+ def perform
9
+ ActiveRecord::Base.connection.clear_query_cache
10
+ source.with_lock do
11
+ source.available_pairs.each do |pair|
12
+ create_rate pair
13
+ end
14
+ source.update_attribute :actual_snapshot_id, snapshot.id
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def source
21
+ @source ||= RateSourceCBRAvg.get!
22
+ end
23
+
24
+ def snapshot
25
+ @snapshot ||= source.snapshots.create!
26
+ end
27
+
28
+ def create_rate(pair)
29
+ er = RateSource.cbr.find_rate_by_currency_pair pair
30
+
31
+ price = (er.sell_price + er.buy_price) / 2.0
32
+
33
+ ExternalRate.create!(
34
+ source: source,
35
+ snapshot: snapshot,
36
+ currency_pair: pair,
37
+ sell_price: price,
38
+ buy_price: price
39
+ )
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+ require 'business_time'
5
+
6
+ module Gera
7
+ # Import rates from Russian Central Bank
8
+ # http://www.cbr.ru/scripts/XML_daily.asp?date_req=08/04/2018
9
+ #
10
+ class CBRRatesWorker
11
+ include Sidekiq::Worker
12
+ include AutoLogger
13
+
14
+ CURRENCIES = %w[USD KZT EUR UAH].freeze
15
+
16
+ CBR_IDS = {
17
+ 'USD' => 'R01235',
18
+ 'KZT' => 'R01335',
19
+ 'EUR' => 'R01239',
20
+ 'UAH' => 'R01720'
21
+ }.freeze
22
+
23
+ ROUND = 15
24
+
25
+ Error = Class.new StandardError
26
+ WrongDate = Class.new Error
27
+
28
+ URL = 'http://www.cbr.ru/scripts/XML_daily.asp'
29
+
30
+ def perform
31
+ ActiveRecord::Base.connection.clear_query_cache
32
+ cbr.with_lock do
33
+ days.each do |date|
34
+ fetch_and_save_rate date
35
+ end
36
+
37
+ make_snapshot
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def currencies
44
+ @currencies ||= CURRENCIES.map { |iso_code| Money::Currency.find! iso_code }
45
+ end
46
+
47
+ def snapshot
48
+ @snapshot ||= cbr.snapshots.create!
49
+ end
50
+
51
+ def avg_snapshot
52
+ @avg_snapshot ||= cbr_avg.snapshots.create!
53
+ end
54
+
55
+ def make_snapshot
56
+ save_snapshot_rate USD, RUB
57
+ save_snapshot_rate KZT, RUB
58
+ save_snapshot_rate EUR, RUB
59
+ save_snapshot_rate UAH, RUB
60
+
61
+ cbr.update_attribute :actual_snapshot_id, snapshot.id
62
+ cbr_avg.update_attribute :actual_snapshot_id, avg_snapshot.id
63
+ end
64
+
65
+ def save_snapshot_rate(cur_from, cur_to)
66
+ pair = CurrencyPair.new cur_from, cur_to
67
+
68
+ min_rate, max_rate = CbrExternalRate
69
+ .where(cur_from: cur_from.iso_code, cur_to: cur_to.iso_code)
70
+ .order('date asc')
71
+ .last(2)
72
+ .sort
73
+
74
+ raise "No minimal rate #{cur_from}, #{cur_to}" unless min_rate
75
+ raise "No maximal rate #{cur_from}, #{cur_to}" unless max_rate
76
+
77
+ ExternalRate.create!(
78
+ source: cbr,
79
+ snapshot: snapshot,
80
+ currency_pair: pair,
81
+ rate_value: min_rate.rate
82
+ )
83
+
84
+ ExternalRate.create!(
85
+ source: cbr,
86
+ snapshot: snapshot,
87
+ currency_pair: pair.inverse,
88
+ rate_value: 1.0 / max_rate.rate
89
+ )
90
+
91
+ avg_rate = (max_rate.rate + min_rate.rate) / 2.0
92
+
93
+ ExternalRate.create!(
94
+ source: cbr_avg,
95
+ snapshot: avg_snapshot,
96
+ currency_pair: pair,
97
+ rate_value: avg_rate
98
+ )
99
+
100
+ ExternalRate.create!(
101
+ source: cbr_avg,
102
+ snapshot: avg_snapshot,
103
+ currency_pair: pair.inverse,
104
+ rate_value: 1.0 / avg_rate
105
+ )
106
+ end
107
+
108
+ def days
109
+ today = Date.today
110
+ logger.info "Start import for #{today}"
111
+
112
+ [
113
+ 1.business_day.ago(today),
114
+ today.yesterday.yesterday,
115
+ today.yesterday,
116
+ today,
117
+ today.tomorrow,
118
+ 1.business_day.from_now(today)
119
+ ].uniq.sort
120
+ end
121
+
122
+ def fetch_and_save_rate(date)
123
+ fetch_rates date
124
+ rescue WrongDate => err
125
+ logger.warn err
126
+
127
+ # HTTP redirection loop: http://www.cbr.ru/scripts/XML_daily.asp?date_req=09/01/2019
128
+ rescue RuntimeError => err
129
+ raise err unless err.message.include? 'HTTP redirection loop'
130
+
131
+ logger.error err
132
+ end
133
+
134
+ def cbr_avg
135
+ @cbr_avg ||= RateSourceCBRAvg.get!
136
+ end
137
+
138
+ def cbr
139
+ @cbr ||= RateSourceCBR.get!
140
+ end
141
+
142
+ def fetch_rates(date)
143
+ return if CbrExternalRate.where(date: date, cur_from: currencies.map(&:iso_code)).count == currencies.count
144
+
145
+ root = build_root date
146
+
147
+ currencies.each do |cur|
148
+ save_rate get_rate(root, CBR_IDS[cur.iso_code]), cur, date unless CbrExternalRate.where(date: date, cur_from: cur.iso_code).exists?
149
+ end
150
+ end
151
+
152
+ def build_root(date)
153
+ uri = URI.parse URL
154
+ uri.query = 'date_req=' + date.strftime('%d/%m/%Y')
155
+
156
+ logger.info "fetch rates for #{date} from #{uri}"
157
+
158
+ doc = Nokogiri::XML open uri
159
+ root = doc.xpath('/ValCurs')
160
+
161
+ root_date = root.attr('Date').text
162
+ validate_date = date.strftime('%d.%m.%Y')
163
+
164
+ return root if validate_date == root_date
165
+
166
+ raise WrongDate, "Request and response dates are different #{uri}: #{validate_date} <> #{root_date}"
167
+ end
168
+
169
+ def get_rate(root, id)
170
+ valute = root.xpath("Valute[@ID=\"#{id}\"]")
171
+ original_rate = valute.xpath('Value').text.sub(',', '.').to_f
172
+ nominal = valute.xpath('Nominal').text.sub(',', '.').to_f
173
+ OpenStruct.new original_rate: original_rate, nominal: nominal
174
+ end
175
+
176
+ def save_rate(rate_struct, cur, date)
177
+ original_rate = rate_struct.original_rate
178
+ nominal = rate_struct.nominal
179
+
180
+ rate = (original_rate / nominal).round(ROUND)
181
+
182
+ CbrExternalRate.create!(
183
+ cur_from: cur.iso_code,
184
+ cur_to: RUB.iso_code,
185
+ rate: rate,
186
+ original_rate: original_rate,
187
+ nominal: nominal,
188
+ date: date
189
+ )
190
+ end
191
+ end
192
+ end