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 +5 -5
- data/lib/faraday/csv.rb +6 -0
- data/lib/faraday/response/parse_csv.rb +74 -0
- data/lib/moex.rb +159 -2
- data/lib/moex/resources.rb +10 -0
- data/lib/moex/resources/price.rb +14 -0
- data/lib/moex/resources/security.rb +17 -0
- metadata +77 -26
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.travis.yml +0 -5
- data/Gemfile +0 -4
- data/README.md +0 -36
- data/Rakefile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/moex/version.rb +0 -3
- data/moex.gemspec +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d833f6061d7a6784b6a87036344d55c3dd82733f11f770179796948135dee2cc
|
4
|
+
data.tar.gz: 7a7d0bee03c1f529b50dbfa28fd760d2f634ce1c4277440157a756c6d4d30acb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 251820c76910dcae01fdce3f0871c2295a2578aa26fa0b7e0354ce86c48ae290303971461ca0e09f92193c512d7cdfb220386e6285f9aece739a9b4c8772b29f
|
7
|
+
data.tar.gz: 9fc193be6b1cb6bfe9c1635cb9600161c54953326a154f48560b91b227d9ec9ce656df3b893a5545ddc09286b48bee923c0e070248c228aaff49a66af9c1c334
|
data/lib/faraday/csv.rb
ADDED
@@ -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
|
data/lib/moex.rb
CHANGED
@@ -1,5 +1,162 @@
|
|
1
|
-
|
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
|
-
#
|
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,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.
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- konstantin
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: faraday
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
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: '
|
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: '
|
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: '
|
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
|
-
|
56
|
-
|
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
|
-
-
|
62
|
-
-
|
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/
|
71
|
-
- moex.
|
72
|
-
|
73
|
-
|
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.
|
143
|
+
rubygems_version: 2.7.3
|
92
144
|
signing_key:
|
93
145
|
specification_version: 4
|
94
|
-
summary:
|
146
|
+
summary: Moscow Exchange ISS
|
95
147
|
test_files: []
|
96
|
-
has_rdoc:
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
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
data/bin/console
DELETED
@@ -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
data/lib/moex/version.rb
DELETED
data/moex.gemspec
DELETED
@@ -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
|