moex 0.0.0 → 0.1.0

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 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