gera 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +67 -0
- data/Rakefile +43 -0
- data/app/assets/config/gera_manifest.js +2 -0
- data/app/assets/javascripts/gera/application.js +14 -0
- data/app/assets/stylesheets/gera/application.css +15 -0
- data/app/authorizers/gera/application_authorizer.rb +6 -0
- data/app/authorizers/gera/currency_rate_authorizer.rb +9 -0
- data/app/authorizers/gera/currency_rate_mode_authorizer.rb +7 -0
- data/app/authorizers/gera/direction_rate_authorizer.rb +8 -0
- data/app/authorizers/gera/exchange_rate_authorizer.rb +8 -0
- data/app/authorizers/gera/payment_system_authorizer.rb +13 -0
- data/app/authorizers/gera/rate_source_authorizer.rb +7 -0
- data/app/controllers/gera/application_controller.rb +30 -0
- data/app/controllers/gera/currencies_controller.rb +8 -0
- data/app/controllers/gera/currency_rate_history_intervals_controller.rb +53 -0
- data/app/controllers/gera/currency_rate_mode_snapshots_controller.rb +83 -0
- data/app/controllers/gera/currency_rate_modes_controller.rb +60 -0
- data/app/controllers/gera/currency_rates_controller.rb +70 -0
- data/app/controllers/gera/direction_rate_history_intervals_controller.rb +62 -0
- data/app/controllers/gera/direction_rates_controller.rb +28 -0
- data/app/controllers/gera/exchange_rates_controller.rb +25 -0
- data/app/controllers/gera/external_rate_snapshots_controller.rb +35 -0
- data/app/controllers/gera/external_rates_controller.rb +28 -0
- data/app/controllers/gera/payment_systems_controller.rb +78 -0
- data/app/controllers/gera/rate_sources_controller.rb +8 -0
- data/app/decorators/gera/currency_rate_decorator.rb +148 -0
- data/app/decorators/gera/direction_rate_decorator.rb +47 -0
- data/app/decorators/gera/payment_system_decorator.rb +19 -0
- data/app/helpers/gera/application_helper.rb +110 -0
- data/app/helpers/gera/currency_rate_helper.rb +19 -0
- data/app/helpers/gera/currency_rate_modes_helper.rb +73 -0
- data/app/helpers/gera/direction_rate_helper.rb +36 -0
- data/app/helpers/gera/exchange_rates_helper.rb +21 -0
- data/app/jobs/gera/application_job.rb +6 -0
- data/app/models/concerns/gera/currency_pair_generator.rb +14 -0
- data/app/models/concerns/gera/currency_pair_support.rb +42 -0
- data/app/models/concerns/gera/currency_rate_mode_builder_support.rb +35 -0
- data/app/models/concerns/gera/direction_support.rb +14 -0
- data/app/models/concerns/gera/history_interval_concern.rb +46 -0
- data/app/models/gera/application_record.rb +8 -0
- data/app/models/gera/cbr_external_rate.rb +13 -0
- data/app/models/gera/cross_rate_mode.rb +13 -0
- data/app/models/gera/currency_rate.rb +78 -0
- data/app/models/gera/currency_rate_history_interval.rb +25 -0
- data/app/models/gera/currency_rate_history_interval_filter.rb +30 -0
- data/app/models/gera/currency_rate_mode.rb +20 -0
- data/app/models/gera/currency_rate_mode_snapshot.rb +26 -0
- data/app/models/gera/currency_rate_snapshot.rb +12 -0
- data/app/models/gera/direction.rb +44 -0
- data/app/models/gera/direction_rate.rb +136 -0
- data/app/models/gera/direction_rate_history_interval.rb +33 -0
- data/app/models/gera/direction_rate_history_interval_filter.rb +30 -0
- data/app/models/gera/direction_rate_snapshot.rb +7 -0
- data/app/models/gera/exchange_rate.rb +130 -0
- data/app/models/gera/external_rate.rb +38 -0
- data/app/models/gera/external_rate_snapshot.rb +20 -0
- data/app/models/gera/payment_system.rb +98 -0
- data/app/models/gera/rate_source.rb +77 -0
- data/app/models/gera/rate_source_auto.rb +25 -0
- data/app/models/gera/rate_source_bitfinex.rb +9 -0
- data/app/models/gera/rate_source_cbr.rb +13 -0
- data/app/models/gera/rate_source_cbr_avg.rb +6 -0
- data/app/models/gera/rate_source_exmo.rb +9 -0
- data/app/models/gera/rate_source_manual.rb +13 -0
- data/app/views/application/_exchange_rate_monitor.slim +1 -0
- data/app/views/gera/application/_attributes_table.slim +5 -0
- data/app/views/gera/application/_currencies_table.slim +18 -0
- data/app/views/gera/application/_currency_rate_mode_snapshots.slim +6 -0
- data/app/views/gera/application/_direction_rate.slim +6 -0
- data/app/views/gera/application/_directions_submenu.slim +11 -0
- data/app/views/gera/application/_directions_table.slim +15 -0
- data/app/views/gera/application/_exchange_rate.slim +6 -0
- data/app/views/gera/application/_external_rate.slim +6 -0
- data/app/views/gera/application/_header.slim +12 -0
- data/app/views/gera/application/_table.slim +28 -0
- data/app/views/gera/application/_topnav.slim +21 -0
- data/app/views/gera/application/direction_details.slim +75 -0
- data/app/views/gera/application/page.html.slim +1 -0
- data/app/views/gera/currencies/index.html.slim +20 -0
- data/app/views/gera/currency_rate_history_intervals/_filter.slim +11 -0
- data/app/views/gera/currency_rate_history_intervals/index.slim +39 -0
- data/app/views/gera/currency_rate_mode_snapshots/_snapshot.slim +20 -0
- data/app/views/gera/currency_rate_mode_snapshots/edit.html.slim +30 -0
- data/app/views/gera/currency_rate_modes/_cross_rate_mode_fields.slim +21 -0
- data/app/views/gera/currency_rate_modes/_currency_rate_mode.slim +7 -0
- data/app/views/gera/currency_rate_modes/_form.slim +29 -0
- data/app/views/gera/currency_rate_modes/edit.slim +11 -0
- data/app/views/gera/currency_rate_modes/index.html.slim +12 -0
- data/app/views/gera/currency_rates/_currency_rate.slim +22 -0
- data/app/views/gera/currency_rates/_currency_rate_humanized.slim +35 -0
- data/app/views/gera/currency_rates/diff.slim +12 -0
- data/app/views/gera/currency_rates/index.slim +27 -0
- data/app/views/gera/currency_rates/modes.slim +14 -0
- data/app/views/gera/currency_rates/show.slim +4 -0
- data/app/views/gera/direction_rate_history_intervals/_filter.slim +11 -0
- data/app/views/gera/direction_rate_history_intervals/index.slim +41 -0
- data/app/views/gera/direction_rates/_direction_rate_example_calculator.slim +28 -0
- data/app/views/gera/direction_rates/diff.slim +19 -0
- data/app/views/gera/direction_rates/index.slim +26 -0
- data/app/views/gera/direction_rates/legacy.html.slim +5 -0
- data/app/views/gera/direction_rates/minimals.slim +5 -0
- data/app/views/gera/direction_rates/show.slim +14 -0
- data/app/views/gera/external_rate_snapshots/index.slim +25 -0
- data/app/views/gera/external_rate_snapshots/show.slim +22 -0
- data/app/views/gera/external_rates/_flat_list.slim +22 -0
- data/app/views/gera/external_rates/index.html.slim +28 -0
- data/app/views/gera/external_rates/show.slim +1 -0
- data/app/views/gera/payment_systems/_fields.slim +4 -0
- data/app/views/gera/payment_systems/_fields_table.slim +18 -0
- data/app/views/gera/payment_systems/_form.slim +9 -0
- data/app/views/gera/payment_systems/edit.slim +7 -0
- data/app/views/gera/payment_systems/index.slim +3 -0
- data/app/views/gera/payment_systems/new.slim +9 -0
- data/app/views/gera/payment_systems/show.slim +1 -0
- data/app/views/gera/rate_sources/_list.slim +13 -0
- data/app/views/gera/rate_sources/_rate_source.slim +23 -0
- data/app/views/gera/rate_sources/index.html.slim +6 -0
- data/app/views/layouts/application.html.slim +15 -0
- data/app/views/layouts/gera/blank.html.slim +10 -0
- data/app/workers/concerns/gera/rates_worker.rb +87 -0
- data/app/workers/gera/bitfinex_rates_worker.rb +65 -0
- data/app/workers/gera/cbr_avg_rates_worker.rb +42 -0
- data/app/workers/gera/cbr_rates_worker.rb +192 -0
- data/app/workers/gera/create_history_intervals_worker.rb +38 -0
- data/app/workers/gera/currency_rates_worker.rb +64 -0
- data/app/workers/gera/directions_rates_worker.rb +51 -0
- data/app/workers/gera/exmo_rates_worker.rb +62 -0
- data/app/workers/gera/purge_currency_rates_worker.rb +21 -0
- data/app/workers/gera/purge_direction_rates_worker.rb +30 -0
- data/config/currencies.yml +559 -0
- data/config/routes.rb +30 -0
- data/db/migrate/20180912130000_setup.rb +228 -0
- data/db/migrate/20190314114844_add_is_available_to_payment_systems.rb +7 -0
- data/db/migrate/20190315113046_add_icon_url_to_payment_systems.rb +7 -0
- data/db/migrate/20190315132137_add_commission_to_payment_systems.rb +7 -0
- data/lib/banks/currency_exchange.rb +12 -0
- data/lib/builders/currency_rate_auto_builder.rb +38 -0
- data/lib/builders/currency_rate_builder.rb +60 -0
- data/lib/builders/currency_rate_cross_builder.rb +70 -0
- data/lib/builders/currency_rate_direct_builder.rb +26 -0
- data/lib/gera.rb +32 -0
- data/lib/gera/bitfinex_fetcher.rb +36 -0
- data/lib/gera/configuration.rb +47 -0
- data/lib/gera/currencies_purger.rb +28 -0
- data/lib/gera/currency_pair.rb +93 -0
- data/lib/gera/engine.rb +14 -0
- data/lib/gera/mathematic.rb +156 -0
- data/lib/gera/money_support.rb +89 -0
- data/lib/gera/numeric.rb +25 -0
- data/lib/gera/railtie.rb +8 -0
- data/lib/gera/rate.rb +22 -0
- data/lib/gera/rate_from_multiplicator.rb +54 -0
- data/lib/gera/repositories/currency_rate_modes_repository.rb +37 -0
- data/lib/gera/repositories/currency_rates_repository.rb +25 -0
- data/lib/gera/repositories/direction_rates_repository.rb +46 -0
- data/lib/gera/repositories/exchange_rates_repository.rb +23 -0
- data/lib/gera/repositories/payment_systems_repository.rb +21 -0
- data/lib/gera/repositories/universe.rb +47 -0
- data/lib/gera/version.rb +3 -0
- data/lib/tasks/auto_generate_diagram.rake +6 -0
- data/lib/tasks/gera_tasks.rake +4 -0
- 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,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,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,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
|