moex 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fca36dd554f166eca8871bb9b0f8d325218ba227
4
- data.tar.gz: 71278fa920abe7a293e80bc61410a67a6077bf46
2
+ SHA256:
3
+ metadata.gz: d833f6061d7a6784b6a87036344d55c3dd82733f11f770179796948135dee2cc
4
+ data.tar.gz: 7a7d0bee03c1f529b50dbfa28fd760d2f634ce1c4277440157a756c6d4d30acb
5
5
  SHA512:
6
- metadata.gz: 68774f18cf0dde29087d9f1c78de42717204bc3073e60d6d65ec386b20cf914fb87d34e2c65a77c67847f6b126c5e9601f5606452fb74768d9357934d339eeae
7
- data.tar.gz: fdfaf86236bba3893d9db15e4a88db53c3c1a12ff15600dacf519be6082d4a69d444e6f81ccf346b762ae64ec53897a417e3f833b2108f4b42f32bccb4631790
6
+ metadata.gz: 251820c76910dcae01fdce3f0871c2295a2578aa26fa0b7e0354ce86c48ae290303971461ca0e09f92193c512d7cdfb220386e6285f9aece739a9b4c8772b29f
7
+ data.tar.gz: 9fc193be6b1cb6bfe9c1635cb9600161c54953326a154f48560b91b227d9ec9ce656df3b893a5545ddc09286b48bee923c0e070248c228aaff49a66af9c1c334
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/response/parse_csv'
5
+
6
+ Faraday::Response.register_middleware(csv: Faraday::Response::ParseCSV)
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+
5
+ # HTTP client library
6
+ module Faraday
7
+ # Raised when any encoding error occurs
8
+ EncodingError = Class.new(ClientError)
9
+
10
+ # HTTP response
11
+ class Response
12
+ # Parses response bodies as CSV
13
+ #
14
+ # @example
15
+ #
16
+ # require 'faraday/csv'
17
+ #
18
+ # connection = Faraday.new('https://example.org/') do |faraday|
19
+ # faraday.response :csv, headers: :first_row
20
+ # faraday.adapter Faraday.default_adapter
21
+ # end
22
+ #
23
+ # response = connection.get('/data.csv')
24
+ # response.body.first #=> <CSV::Row "foo":"bar">
25
+ #
26
+ # @api private
27
+ class ParseCSV < Middleware
28
+ # Creates a new instance of CSV parsing middleware
29
+ #
30
+ # @param app [#call, nil] Rack app
31
+ # @param opts [Hash] CSV parsing options
32
+ # @see CSV.new
33
+ def initialize(app = nil, opts = {})
34
+ @opts = opts
35
+
36
+ super(app)
37
+ end
38
+
39
+ CSV_CONTENT_TYPE_RE = %r{
40
+ \A # Start
41
+ text/csv(?:;\s* # MIME type
42
+ charset=(?<charset>[a-z0-9\-]+))? # Optional charset value
43
+ \z # End
44
+ }xi
45
+
46
+ private_constant :CSV_CONTENT_TYPE_RE
47
+
48
+ TARGET_ENCODING = Encoding::UTF_8
49
+ private_constant :TARGET_ENCODING
50
+
51
+ private
52
+
53
+ attr_reader :opts
54
+
55
+ def on_complete(env) # rubocop:disable MethodLength,AbcSize
56
+ return unless env.parse_body?
57
+
58
+ content_type = env.response_headers[:content_type]
59
+ csv_content_type = content_type.match(CSV_CONTENT_TYPE_RE)
60
+
61
+ return unless csv_content_type
62
+
63
+ charset = csv_content_type[:charset]
64
+ env.body.encode!(TARGET_ENCODING, charset) if charset
65
+
66
+ env.body = CSV.parse(env.body, opts)
67
+ rescue ::EncodingError => error
68
+ raise Faraday::EncodingError.new(error, env.response)
69
+ rescue CSV::MalformedCSVError => error
70
+ raise Faraday::ParsingError.new(error, env.response)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,162 @@
1
- require "moex/version"
1
+ # frozen_string_literal: true
2
2
 
3
+ require 'faraday'
4
+ require 'faraday/csv'
5
+ require 'moex/resources'
6
+ require 'bigdecimal'
7
+
8
+ # Moscow Exchange ISS
9
+ #
10
+ # @see http://www.moex.com/a2920 Informational & Statistical Server (ISS)
3
11
  module Moex
4
- # Your code goes here...
12
+ # Raised when the ISS response is unsuccessful or invalid, or in case
13
+ # of any connection errors
14
+ ClientError = Faraday::ClientError
15
+
16
+ # rubocop:disable Metrics/MethodLength, Metrics/LineLength
17
+
18
+ # List securities
19
+ #
20
+ # @param params [Hash]
21
+ # @option params [String] security_group security group
22
+ # @option params [String] collection security collection
23
+ # @raise [Moex::ClientError] if the ISS response is unsuccessful or
24
+ # invalid, or in case of any connection errors
25
+ # @return [Array<Resources::Security>] the list of securities
26
+ # @see https://iss.moex.com/iss/securitygroups
27
+ # List of security groups
28
+ # @see https://iss.moex.com/iss/securitygroups/stock_shares/collections
29
+ # List of stock collections
30
+ # @see https://iss.moex.com/iss/securitygroups/stock_bonds/collections
31
+ # List of bond collections
32
+ def self.securities(params)
33
+ Enumerator.new do |yielder|
34
+ start = 0
35
+ security_table = load_securities(params, start: start)
36
+
37
+ loop do
38
+ break if empty_table?(security_table)
39
+
40
+ security_table.each do |row|
41
+ yielder << Resources::Security.new(
42
+ row.fetch(:isin),
43
+ row.fetch(:secid),
44
+ row.fetch(:shortname)
45
+ )
46
+ end
47
+
48
+ start += security_table.size
49
+ security_table = load_securities(params, start: start)
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.load_securities(params, start:)
55
+ url = format('/iss/securitygroups/%<security_group>s/collections/%<collection>s/securities.csv', params)
56
+
57
+ response = connection.get do |request|
58
+ request.url(url)
59
+ request.params['iss.only'] = 'securities'
60
+ request.params['iss.delimiter'] = ';'
61
+ request.params['iss.dp'] = 'point'
62
+ request.params['start'] = start
63
+ end
64
+
65
+ response.body
66
+ end
67
+
68
+ private_class_method :load_securities
69
+
70
+ def self.empty_table?(table)
71
+ return true if table.empty?
72
+
73
+ # For some reason, if `start` is greater that a table size, then
74
+ # the ISS responds with a single table row containing only null
75
+ # values.
76
+ if table.size == 1
77
+ row = table[0]
78
+
79
+ return row.all? { |_key, value| value.nil? }
80
+ end
81
+
82
+ false
83
+ end
84
+
85
+ private_class_method :empty_table?
86
+
87
+ # List security prices
88
+ #
89
+ # @param date [String, nil] the date, format YYYY-MM-DD
90
+ # @raise [Moex::ClientError] if the ISS response is unsuccessful or
91
+ # invalid, or in case of any connection errors
92
+ # @return [Array<Resources::Price>] the list of security prices
93
+ def self.prices(date: nil)
94
+ Enumerator.new do |yielder|
95
+ start = 0
96
+ price_table = load_prices(date: date, start: start)
97
+
98
+ loop do
99
+ break if price_table.empty?
100
+
101
+ price_table.each do |row|
102
+ yielder << Resources::Price.new(
103
+ row.fetch(:secid),
104
+ row.fetch(:close)
105
+ )
106
+ end
107
+
108
+ price_table = load_prices(date: date, start: start += price_table.size)
109
+ end
110
+ end
111
+ end
112
+
113
+ def self.load_prices(date:, start:)
114
+ response = connection.get do |request|
115
+ request.url '/iss/history/engines/stock/markets/shares/boards/tqbr/securities.csv'
116
+ request.params['iss.only'] = 'history'
117
+ request.params['iss.delimiter'] = ';'
118
+ request.params['iss.dp'] = 'point'
119
+ request.params['date'] = date if date
120
+ request.params['start'] = start
121
+ end
122
+
123
+ response.body
124
+ end
125
+
126
+ private_class_method :load_prices
127
+
128
+ # rubocop:enable all
129
+
130
+ MOEX_ISS_API_BASE_URL = 'https://iss.moex.com/'
131
+ private_constant :MOEX_ISS_API_BASE_URL
132
+
133
+ map_field = lambda do |field, field_info|
134
+ return unless field
135
+ return BigDecimal(field) if field_info.header == :close
136
+
137
+ field
138
+ end
139
+
140
+ CSV_PARSE_OPTS = {
141
+ col_sep: ';',
142
+ skip_blanks: true,
143
+ skip_lines: /^\w+$/,
144
+ liberal_parsing: true,
145
+ headers: :first_row,
146
+ header_converters: :symbol,
147
+ quote_char: "'",
148
+ converters: map_field
149
+ }.freeze
150
+
151
+ private_constant :CSV_PARSE_OPTS
152
+
153
+ def self.connection
154
+ @connection ||= Faraday.new(MOEX_ISS_API_BASE_URL) do |connection|
155
+ connection.response :raise_error
156
+ connection.response :csv, CSV_PARSE_OPTS
157
+ connection.adapter Faraday.default_adapter
158
+ end
159
+ end
160
+
161
+ private_class_method :connection
5
162
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moex
4
+ # Resources
5
+ module Resources
6
+ end
7
+ end
8
+
9
+ require 'moex/resources/price'
10
+ require 'moex/resources/security'
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moex
4
+ module Resources
5
+ # Security price
6
+ #
7
+ # @!attribute sec_id
8
+ # @return [String] security ticker symbol
9
+ #
10
+ # @!attribute close_price
11
+ # @return [Numeric] closing price
12
+ Price = Struct.new(:sec_id, :close_price)
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moex
4
+ module Resources
5
+ # Stock or bond
6
+ #
7
+ # @!attribute isin
8
+ # @return [String] international security identification number
9
+ #
10
+ # @!attribute sec_id
11
+ # @return [String] security ticker symbol
12
+ #
13
+ # @!attribute short_name
14
+ # @return [String] security name
15
+ Security = Struct.new(:isin, :sec_id, :short_name)
16
+ end
17
+ end
metadata CHANGED
@@ -1,45 +1,87 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - soylent
7
+ - konstantin
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-23 00:00:00.000000000 Z
11
+ date: 2018-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: faraday
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.12'
19
+ version: '0.13'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
20
34
  type: :development
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '1.12'
40
+ version: '0.11'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '10.0'
47
+ version: '12.0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '10.0'
54
+ version: '12.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.51'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.51'
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
43
85
  requirement: !ruby/object:Gem::Requirement
44
86
  requirements:
45
87
  - - "~>"
@@ -52,25 +94,35 @@ dependencies:
52
94
  - - "~>"
53
95
  - !ruby/object:Gem::Version
54
96
  version: '3.0'
55
- description: Write a longer description or delete this line.
56
- email:
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ description:
112
+ email: konstantin@papkovskiy.com
57
113
  executables: []
58
114
  extensions: []
59
115
  extra_rdoc_files: []
60
116
  files:
61
- - ".gitignore"
62
- - ".rspec"
63
- - ".travis.yml"
64
- - Gemfile
65
- - README.md
66
- - Rakefile
67
- - bin/console
68
- - bin/setup
117
+ - lib/faraday/csv.rb
118
+ - lib/faraday/response/parse_csv.rb
69
119
  - lib/moex.rb
70
- - lib/moex/version.rb
71
- - moex.gemspec
72
- homepage: https://github.com/soylent/moex
73
- licenses: []
120
+ - lib/moex/resources.rb
121
+ - lib/moex/resources/price.rb
122
+ - lib/moex/resources/security.rb
123
+ homepage: https://rubygems.org/gems/moex
124
+ licenses:
125
+ - MIT
74
126
  metadata: {}
75
127
  post_install_message:
76
128
  rdoc_options: []
@@ -88,9 +140,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
140
  version: '0'
89
141
  requirements: []
90
142
  rubyforge_project:
91
- rubygems_version: 2.6.6
143
+ rubygems_version: 2.7.3
92
144
  signing_key:
93
145
  specification_version: 4
94
- summary: Write a short summary, because Rubygems requires one.
146
+ summary: Moscow Exchange ISS
95
147
  test_files: []
96
- has_rdoc:
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --format documentation
2
- --color
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.3.1
5
- before_install: gem install bundler -v 1.12.5
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in moex.gemspec
4
- gemspec
data/README.md DELETED
@@ -1,36 +0,0 @@
1
- # Moex
2
-
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/moex`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
6
-
7
- ## Installation
8
-
9
- Add this line to your application's Gemfile:
10
-
11
- ```ruby
12
- gem 'moex'
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle
18
-
19
- Or install it yourself as:
20
-
21
- $ gem install moex
22
-
23
- ## Usage
24
-
25
- TODO: Write usage instructions here
26
-
27
- ## Development
28
-
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
-
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
-
33
- ## Contributing
34
-
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/moex.
36
-
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "moex"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,3 +0,0 @@
1
- module Moex
2
- VERSION = "0.0.0"
3
- end
@@ -1,23 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'moex/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "moex"
8
- spec.version = Moex::VERSION
9
- spec.authors = ["soylent"]
10
-
11
- spec.summary = %q{Write a short summary, because Rubygems requires one.}
12
- spec.description = %q{Write a longer description or delete this line.}
13
- spec.homepage = "https://github.com/soylent/moex"
14
-
15
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
- spec.bindir = "exe"
17
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
- spec.require_paths = ["lib"]
19
-
20
- spec.add_development_dependency "bundler", "~> 1.12"
21
- spec.add_development_dependency "rake", "~> 10.0"
22
- spec.add_development_dependency "rspec", "~> 3.0"
23
- end