eodhd 0.19.5
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/CHANGELOG +195 -0
- data/Gemfile +3 -0
- data/README.md +160 -0
- data/Rakefile +28 -0
- data/eodhd.gemspec +52 -0
- data/lib/Eodhd/Client.rb +143 -0
- data/lib/Eodhd/DefaultLogger.rb +30 -0
- data/lib/Eodhd/EodBulkLastDay.rb +56 -0
- data/lib/Eodhd/EodData.rb +56 -0
- data/lib/Eodhd/Error.rb +16 -0
- data/lib/Eodhd/Exchange.rb +50 -0
- data/lib/Eodhd/ExchangeSymbol.rb +50 -0
- data/lib/Eodhd/Fundamentals/AnalystRatings.rb +29 -0
- data/lib/Eodhd/Fundamentals/ESGScores.rb +37 -0
- data/lib/Eodhd/Fundamentals/Earnings/AnnualEntry.rb +21 -0
- data/lib/Eodhd/Fundamentals/Earnings/HistoryEntry.rb +33 -0
- data/lib/Eodhd/Fundamentals/Earnings/TrendEntry.rb +65 -0
- data/lib/Eodhd/Fundamentals/Earnings.rb +30 -0
- data/lib/Eodhd/Fundamentals/Financials/BalanceSheet/Entry.rb +147 -0
- data/lib/Eodhd/Fundamentals/Financials/BalanceSheet.rb +28 -0
- data/lib/Eodhd/Fundamentals/Financials/CashFlow/Entry.rb +83 -0
- data/lib/Eodhd/Fundamentals/Financials/CashFlow.rb +28 -0
- data/lib/Eodhd/Fundamentals/Financials/IncomeStatement/Entry.rb +87 -0
- data/lib/Eodhd/Fundamentals/Financials/IncomeStatement.rb +28 -0
- data/lib/Eodhd/Fundamentals/Financials.rb +25 -0
- data/lib/Eodhd/Fundamentals/General.rb +85 -0
- data/lib/Eodhd/Fundamentals/Highlights.rb +63 -0
- data/lib/Eodhd/Fundamentals/Holders/Fund.rb +31 -0
- data/lib/Eodhd/Fundamentals/Holders/Institution.rb +31 -0
- data/lib/Eodhd/Fundamentals/Holders.rb +27 -0
- data/lib/Eodhd/Fundamentals/InsiderTransaction.rb +35 -0
- data/lib/Eodhd/Fundamentals/OutstandingShares/Entry.rb +25 -0
- data/lib/Eodhd/Fundamentals/OutstandingShares.rb +26 -0
- data/lib/Eodhd/Fundamentals/SharesStats.rb +33 -0
- data/lib/Eodhd/Fundamentals/SplitsDividends.rb +31 -0
- data/lib/Eodhd/Fundamentals/Technicals.rb +33 -0
- data/lib/Eodhd/Fundamentals/Valuation.rb +29 -0
- data/lib/Eodhd/Fundamentals.rb +92 -0
- data/lib/Eodhd/Intraday.rb +76 -0
- data/lib/Eodhd/VERSION.rb +3 -0
- data/lib/Eodhd/Validations.rb +78 -0
- data/lib/Eodhd/WebSocketClient.rb +130 -0
- data/lib/Hash/x_www_form_urlencode.rb +7 -0
- data/lib/Thoran/Hash/XWwwFormUrlencode/x_www_form_urlencode.rb +27 -0
- data/lib/Thoran/String/Urlencode/urlencode.rb +29 -0
- data/lib/eodhd.rb +78 -0
- data/test/Eodhd/Client_test.rb +111 -0
- data/test/Eodhd/Fundamentals_test.rb +163 -0
- data/test/Eodhd/VERSION_test.rb +20 -0
- data/test/Eodhd/Validations_test.rb +135 -0
- data/test/Eodhd/WebSocketClient_test.rb +99 -0
- data/test/Eodhd_test.rb +73 -0
- data/test/helper.rb +17 -0
- metadata +218 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cf8b67ed05dcf07f838df21a530d42c07824fd548ce5956f0425356c6e20ab9b
|
|
4
|
+
data.tar.gz: 107375d103ae2afebd038183c256c7c4d387d47287bbcf524ef5afe058c0de24
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e84643904cbe85f5d7b67961927b071e9966020677f6c48bd3c5070e4e47bcd90c18ac72cb1ed9f0d5c6158978624109136a2a3935a2373c2f8208862ee05980
|
|
7
|
+
data.tar.gz: 2a6f1e38654db71f172aa88e40ef96e3e55a3f98762930648c0b24986c6f0714cb480503386920c31eb3ee7a3d5c018fefb95b9fb15307806aa11f0bc79a10e4
|
data/CHANGELOG
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 20260915
|
|
4
|
+
|
|
5
|
+
0.19.5: + eodhd.gemspec, so that the gem resolves under both names.
|
|
6
|
+
|
|
7
|
+
1. + eodhd.gemspec: eodhd.rb.gemspec with /eodhd.rb/eodhd/ in spec.name and in spec.files, upon measurand 0.1.4's pattern.
|
|
8
|
+
2. ~ Gemfile: /gemspec/gemspec name: 'eodhd.rb'/, bundler refusing to parse a Gemfile whose bare gemspec directive finds more than one gemspec to choose from.
|
|
9
|
+
3. ~ Eodhd::VERSION: /0.19.4/0.19.5/
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## 20260910
|
|
13
|
+
|
|
14
|
+
0.19.4: ~ lib/Eodhd.rb: + require_relative './Eodhd/VERSION'; + test/Eodhd/VERSION_test.rb
|
|
15
|
+
|
|
16
|
+
1. ~ lib/Eodhd.rb: + `require_relative './Eodhd/VERSION'`, last among the requires. The file has been present and unreached, so `require 'eodhd'` left Eodhd::VERSION undefined and the gemspec was the only thing loading it.
|
|
17
|
+
2. + test/Eodhd/VERSION_test.rb: that Eodhd::VERSION is a string, that it is three numbers separated by dots, and that it matches the newest entry in the changelog. Modelled on moby's, which is where the convention comes from.
|
|
18
|
+
3. The test loads the library rather than VERSION.rb, which is the whole of its value: run against v0.19.3 it fails with "uninitialized constant Eodhd::VERSION". Nine libraries were in that state and not one of them carried this test, which is how it went unseen.
|
|
19
|
+
4. ~ Eodhd::VERSION: /0.19.3/0.19.4/
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## 20260821
|
|
23
|
+
|
|
24
|
+
0.19.3: Rewrite the CHANGELOG in the form changelogger writes.
|
|
25
|
+
|
|
26
|
+
1. ~ CHANGELOG: The Keep a Changelog form, `## [version] - date, summary` over `- ` bullets, --> the form the rest of these gems use: a `## YYYYMMDD` heading per date, newest first, holding each of that date's versions as a summary line over numbered items. No item was reworded, dropped or reordered, and all 111 are still here.
|
|
27
|
+
2. ~ CHANGELOG: 20260719 carried three headings, for 0.18.1, 0.19.0 and 0.19.1, so one day's work read as three; 20251005 carried two, for 0.16.0 and 0.17.0. One heading each now, the versions under them in the same order they were in.
|
|
28
|
+
3. ~ CHANGELOG: The version summaries are the commit subjects, which three of them had not been: 0.19.2 read "populate the .gitignore" against a commit saying `~ .gitignore: + current default contents`, 0.19.1 likewise, and 0.19.0 differed in case. Every other repo here keeps the two identical, which is what makes a summary checkable against something.
|
|
29
|
+
4. ~ CHANGELOG: /# eodhd.rb\/CHANGELOG/# CHANGELOG/, the file's own name being no part of what it records.
|
|
30
|
+
5. ~ Eodhd/VERSION: /0.19.2/0.19.3/
|
|
31
|
+
|
|
32
|
+
0.19.2: ~ .gitignore: + current default contents
|
|
33
|
+
|
|
34
|
+
1. ~ .gitignore: + current default contents. The file held only its filename comment, having been committed that way.
|
|
35
|
+
2. ~ Eodhd/VERSION: /0.19.1/0.19.2/
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## 20260719
|
|
39
|
+
|
|
40
|
+
0.19.1: ~ README.md: + a blank line before each code fence.
|
|
41
|
+
|
|
42
|
+
1. ~ README.md: + a blank line before each code fence, without which the highlighting does not render.
|
|
43
|
+
2. ~ Eodhd/VERSION: /0.19.0/0.19.1/
|
|
44
|
+
|
|
45
|
+
0.19.0: Complete the fundamentals model wrapping.
|
|
46
|
+
|
|
47
|
+
1. + Eodhd::Fundamentals::Financials::BalanceSheet::Entry: Typed entry for a balance sheet period (64 fields).
|
|
48
|
+
2. + Eodhd::Fundamentals::Financials::IncomeStatement::Entry: Typed entry for an income statement period (34 fields).
|
|
49
|
+
3. + Eodhd::Fundamentals::Financials::CashFlow::Entry: Typed entry for a cash flow period (32 fields).
|
|
50
|
+
4. + Eodhd::Fundamentals::Earnings::AnnualEntry
|
|
51
|
+
5. + Eodhd::Fundamentals::Earnings::HistoryEntry
|
|
52
|
+
6. + Eodhd::Fundamentals::Earnings::TrendEntry
|
|
53
|
+
7. + Eodhd::Fundamentals::Holders::Institution: Typed entry for an institutional holder.
|
|
54
|
+
8. + Eodhd::Fundamentals::Holders::Fund: Typed entry for a fund holder.
|
|
55
|
+
9. + Eodhd::Fundamentals::InsiderTransaction: Typed entry for an insider transaction.
|
|
56
|
+
10. + Eodhd::Fundamentals::OutstandingShares::Entry: Typed entry for an outstanding shares data point.
|
|
57
|
+
11. + Eodhd::Fundamentals::OutstandingShares: Wraps annual and quarterly OutstandingShares::Entry arrays.
|
|
58
|
+
12. + Eodhd::Fundamentals::Financials::BalanceSheet
|
|
59
|
+
13. + Eodhd::Fundamentals::Financials::IncomeStatement
|
|
60
|
+
14. + Eodhd::Fundamentals::Financials::CashFlow
|
|
61
|
+
15. ~ Eodhd::Fundamentals::Financials: Build BalanceSheet/IncomeStatement/CashFlow instead of the shared Statement.
|
|
62
|
+
16. ~ Eodhd::Fundamentals::Financials::BalanceSheet#yearly, #quarterly: Now sorted arrays of typed entries, was raw hashes.
|
|
63
|
+
17. ~ Eodhd::Fundamentals::Earnings#annual, #history, #trend: Now sorted arrays of typed entries, was raw hashes.
|
|
64
|
+
18. ~ Eodhd::Fundamentals::Holders#institutions, #funds: Now arrays of typed entries, was raw hashes.
|
|
65
|
+
19. ~ Eodhd::Fundamentals#insider_transactions: Now a sorted array of InsiderTransaction, was a raw hash.
|
|
66
|
+
20. ~ Eodhd::Fundamentals#outstanding_shares: Now an OutstandingShares object, was a raw hash.
|
|
67
|
+
21. + test/Eodhd/Fundamentals_test.rb: Cover the newly wrapped entry types and collections.
|
|
68
|
+
22. - Eodhd::Fundamentals::Financials::Statement: Replaced by the three per-statement classes.
|
|
69
|
+
23. ~ Eodhd/VERSION: /0.18.1/0.19.0/
|
|
70
|
+
|
|
71
|
+
0.18.1: urlencode helper naming consistency
|
|
72
|
+
|
|
73
|
+
1. /Thoran::String::UrlEncode#url_encode/Thoran::String::Urlencode#urlencode/ (url_encode kept as an alias).
|
|
74
|
+
2. /Thoran::Hash::XWwwFormUrlEncode#x_www_form_url_encode/Thoran::Hash::XWwwFormUrlencode#x_www_form_urlencode/ (x_www_form_url_encode kept as an alias).
|
|
75
|
+
3. ~ lib/Hash/x_www_form_urlencode.rb: Renamed urlencode require path.
|
|
76
|
+
4. ~ eodhd.rb.gemspec: - spec.date (RubyGems stamps the build date); tidy spec.require_paths placement.
|
|
77
|
+
5. ~ Eodhd/VERSION: /0.18.0/0.18.1/
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## 20260320
|
|
81
|
+
|
|
82
|
+
0.18.0: + fundamentals endpoint
|
|
83
|
+
|
|
84
|
+
1. + Eodhd::Fundamentals: Model for fundamental data with typed sub-objects.
|
|
85
|
+
2. + Eodhd::Fundamentals::General
|
|
86
|
+
3. + Eodhd::Fundamentals::Highlights
|
|
87
|
+
4. + Eodhd::Fundamentals::Valuation
|
|
88
|
+
5. + Eodhd::Fundamentals::SharesStats
|
|
89
|
+
6. + Eodhd::Fundamentals::Technicals
|
|
90
|
+
7. + Eodhd::Fundamentals::SplitsDividends
|
|
91
|
+
8. + Eodhd::Fundamentals::AnalystRatings
|
|
92
|
+
9. + Eodhd::Fundamentals::Holders
|
|
93
|
+
10. + Eodhd::Fundamentals::ESGScores
|
|
94
|
+
11. + Eodhd::Fundamentals::Earnings
|
|
95
|
+
12. + Eodhd::Fundamentals::Financials
|
|
96
|
+
13. + Eodhd::Fundamentals::Financials::Statement
|
|
97
|
+
14. + Eodhd::Client#fundamentals: Fetch fundamental data for a symbol.
|
|
98
|
+
15. + Eodhd#fundamentals: Facade method for fundamental data.
|
|
99
|
+
16. + test/Eodhd/Fundamentals_test.rb
|
|
100
|
+
17. ~ test/Eodhd/Client_test.rb: + tests for fundamentals endpoint.
|
|
101
|
+
18. ~ test/Eodhd_test.rb: + test for fundamentals facade method.
|
|
102
|
+
19. ~ test/helper.rb: + require 'minitest/mock' for minitest 6 compatibility.
|
|
103
|
+
20. ~ Eodhd: Move initialize, web_socket, stream to private.
|
|
104
|
+
21. ~ Eodhd: + safe navigation (&.) on exchange and exchange_symbol across all methods.
|
|
105
|
+
22. ~ Eodhd#intraday: + exchange_symbol argument.
|
|
106
|
+
23. ~ Eodhd#intraday: - default exchange_code of 'US'
|
|
107
|
+
24. ~ test/: Reorganise test files into test/Eodhd/ subdirectory.
|
|
108
|
+
25. - test/test_all.rb: Previously replaced by Rakefile.
|
|
109
|
+
26. /CHANGELOG.txt/CHANGELOG/
|
|
110
|
+
27. + LICENSE: MIT License.
|
|
111
|
+
28. ~ README.md: Rewrite with intraday, fundamentals, and WebSocket documentation.
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## 20251005
|
|
115
|
+
|
|
116
|
+
0.17.0: + intraday endpoint
|
|
117
|
+
|
|
118
|
+
1. + Eodhd::Intraday: Model for intraday data.
|
|
119
|
+
2. + Eodhd::Client#intraday: Fetch intraday data for US stocks.
|
|
120
|
+
3. + Eodhd#intraday: Facade method for intraday data.
|
|
121
|
+
4. + Eodhd::Validations#validate_interval: Validation of intraday intervals (1m, 5m, 15m, 30m, 1h, 4h, 1d, 1w, 1mo).
|
|
122
|
+
5. ~ Eodhd::Validations#validate_arguments: + interval argument.
|
|
123
|
+
6. ~ test/validations_test.rb: + tests for validate_interval.
|
|
124
|
+
7. ~ test/client_test.rb: + tests for intraday endpoint.
|
|
125
|
+
8. ~ test/eodhd_test.rb: + tests for intraday facade method.
|
|
126
|
+
|
|
127
|
+
0.16.0: + automated tests, + DefaultLogger, fix WebSocket bugs, improve validations
|
|
128
|
+
|
|
129
|
+
1. + Eodhd::DefaultLogger: A shared logger for Client and WebSocketClient.
|
|
130
|
+
2. ~ Eodhd::Client: Use DefaultLogger.
|
|
131
|
+
3. ~ Eodhd::Client#initialize: + logger, use_default_logger arguments.
|
|
132
|
+
4. + Eodhd::Client#use_logging?
|
|
133
|
+
5. ~ Eodhd::Validations: Move validate_arguments() from Client to Validations.
|
|
134
|
+
6. ~ Eodhd::Validations: Remove ! from all validation methods (validate_exchange_code, validate_symbol, etc).
|
|
135
|
+
7. ~ Eodhd::WebSocketClient: Use DefaultLogger, add logger attribute.
|
|
136
|
+
8. ~ Eodhd::WebSocketClient#initialize: + logger, use_default_logger arguments.
|
|
137
|
+
9. + Eodhd::WebSocketClient#use_logging?: Check if logging is enabled.
|
|
138
|
+
10. ~ Eodhd::WebSocketClient::Handler: Remove duplicate logger code.
|
|
139
|
+
11. ~ Eodhd::WebSocketClient::Handler#initialize: + logger argument.
|
|
140
|
+
12. ~ Eodhd::WebSocketClient::Handler#handle_response: Fix JSON parsing with proper StandardError rescue.
|
|
141
|
+
13. ~ Eodhd::WebSocketClient::Handler#log_error: Use instance logger with use_logging? check.
|
|
142
|
+
14. + test/validations_test.rb: Comprehensive validation tests with edge cases.
|
|
143
|
+
15. + test/client_test.rb: REST client tests with VCR for all endpoints.
|
|
144
|
+
16. + test/eodhd_test.rb: Facade delegation tests.
|
|
145
|
+
17. + test/web_socket_client_test.rb: WebSocket client tests with stubbed Iodine.
|
|
146
|
+
18. + test/helper.rb: VCR configuration with WebMock.
|
|
147
|
+
19. ~ eodhd.gemspec: Add iodine as runtime dependency.
|
|
148
|
+
20. ~ .gitignore: + Gemfile.lock.
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
## 20250916
|
|
152
|
+
|
|
153
|
+
0.15.0: Add validations, response logging, a version constant, and CHANGELOG.
|
|
154
|
+
|
|
155
|
+
1. + Eodhd::Validations
|
|
156
|
+
2. + Eodhd::Client#validate_arguments
|
|
157
|
+
3. ~ Eodhd::Client#exchange_symbol_list: Use validate_arguments().
|
|
158
|
+
4. ~ Eodhd::Client#eod_data: Use validate_arguments().
|
|
159
|
+
5. ~ Eodhd::Client#eod_bulk_last_day: Use validate_arguments().
|
|
160
|
+
6. + Eodhd::Client#log_response
|
|
161
|
+
7. ~ Eodhd::Client#do_request: Call log_request() after construction of args.
|
|
162
|
+
8. ~ Eodhd::Client#handle_response: Use log_response().
|
|
163
|
+
9. + Eodhd::VERSION
|
|
164
|
+
10. ~ eodhd.gemspec: Use Eodhd::VERSION and update the date.
|
|
165
|
+
11. + CHANGELOG.txt
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
## 20250326
|
|
169
|
+
|
|
170
|
+
0.14.2: Remove reference to currently unused library UnixTime.
|
|
171
|
+
|
|
172
|
+
1. ~ Eodhd::WebSocketClient: - require 'UnixTime'
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## 20250323
|
|
176
|
+
|
|
177
|
+
0.14.1: Some fixes for the WebSocketClient interface.
|
|
178
|
+
|
|
179
|
+
1. ~ Eodhd::WebSocketClient: + attr_accessor :consumer
|
|
180
|
+
2. ~ Eodhd::WebSocketClient: /attr_accessor :symbols/attr_reader :symbols/
|
|
181
|
+
3. + Eodhd::WebSocketClient#symbols=
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
## 20250321
|
|
185
|
+
|
|
186
|
+
0.14.0: Add WebSockets.
|
|
187
|
+
|
|
188
|
+
1. + Eodhd::WebSocketClient
|
|
189
|
+
2. + Eodhd#web_socket
|
|
190
|
+
3. + Eodhd#stream
|
|
191
|
+
4. + Eodhd#us_quote_stream
|
|
192
|
+
5. + Eodhd#us_trade_stream
|
|
193
|
+
6. + Eodhd#forex_stream
|
|
194
|
+
7. + Eodhd#crypto_stream
|
|
195
|
+
8. ~ Eodhd::Error: lib/Eodhd/Client.rb --> lib/Eodhd/Error.rb
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# eodhd.rb
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Access the eodhd.com API with Ruby.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'eodhd.rb'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ bundle
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or install it yourself as:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
$ gem install eodhd.rb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Setup
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
api_token = 'api_token'
|
|
33
|
+
eodhd = Eodhd.new(api_token: api_token)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### List of Exchanges
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
eodhd.exchanges
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### List of Exchange Symbols
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
exchange = eodhd.exchanges.first
|
|
46
|
+
|
|
47
|
+
eodhd.exchange_symbols(exchange: exchange)
|
|
48
|
+
# OR
|
|
49
|
+
eodhd.exchange_symbols(exchange_code: exchange.code)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Retrieve EOD Data For One Symbol
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
exchange = eodhd.exchanges.first
|
|
56
|
+
exchange_symbol = eodhd.exchange_symbols(exchange: exchange).first
|
|
57
|
+
|
|
58
|
+
eodhd.eod_data(exchange: exchange, exchange_symbol: exchange_symbol)
|
|
59
|
+
# OR
|
|
60
|
+
eodhd.eod_data(exchange_code: exchange.code, exchange_symbol: exchange_symbol)
|
|
61
|
+
# OR
|
|
62
|
+
eodhd.eod_data(exchange: exchange, symbol: exchange_symbol.code)
|
|
63
|
+
# OR
|
|
64
|
+
eodhd.eod_data(exchange_code: exchange.code, symbol: exchange_symbol.code)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Retrieve EOD Data For Multiple Symbols For One Date
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
exchange = eodhd.exchanges.first
|
|
71
|
+
|
|
72
|
+
eodhd.eod_bulk_last_day(exchange: exchange, date: Date.today)
|
|
73
|
+
# OR
|
|
74
|
+
eodhd.eod_bulk_last_day(exchange_code: exchange.code, date: Date.today)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Retrieve Intraday Data
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
eodhd.intraday(symbol: 'AAPL', exchange_code: 'US', interval: '5m')
|
|
81
|
+
# OR with date range (from and to are Unix timestamps)
|
|
82
|
+
eodhd.intraday(symbol: 'AAPL', exchange_code: 'US', interval: '1h', from: 1696118400, to: 1696550400)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Retrieve Fundamental Data
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
fd = eodhd.fundamentals(exchange_code: 'US', symbol: 'AAPL')
|
|
89
|
+
|
|
90
|
+
fd.general
|
|
91
|
+
fd.general.name
|
|
92
|
+
fd.general.sector
|
|
93
|
+
fd.general.industry
|
|
94
|
+
|
|
95
|
+
fd.highlights
|
|
96
|
+
fd.highlights.market_capitalization
|
|
97
|
+
fd.highlights.pe_ratio
|
|
98
|
+
fd.highlights.dividend_yield
|
|
99
|
+
fd.highlights.earnings_share
|
|
100
|
+
|
|
101
|
+
fd.valuation
|
|
102
|
+
fd.valuation.trailing_pe
|
|
103
|
+
fd.valuation.forward_pe
|
|
104
|
+
fd.valuation.price_book_mrq
|
|
105
|
+
|
|
106
|
+
fd.shares_stats
|
|
107
|
+
fd.shares_stats.shares_outstanding
|
|
108
|
+
fd.shares_stats.shares_float
|
|
109
|
+
|
|
110
|
+
fd.financials
|
|
111
|
+
fd.financials.income_statement
|
|
112
|
+
fd.financials.income_statement.quarterly
|
|
113
|
+
fd.financials.income_statement.yearly
|
|
114
|
+
fd.financials.balance_sheet.quarterly
|
|
115
|
+
fd.financials.cash_flow.quarterly
|
|
116
|
+
|
|
117
|
+
fd.earnings
|
|
118
|
+
fd.earnings.history
|
|
119
|
+
fd.earnings.trend
|
|
120
|
+
fd.earnings.annual
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Use the filter parameter to request only specific sections
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
eodhd.fundamentals(exchange_code: 'US', symbol: 'AAPL', filter: 'General')
|
|
127
|
+
eodhd.fundamentals(exchange_code: 'US', symbol: 'AAPL', filter: 'Financials::Income_Statement::quarterly')
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### WebSockets
|
|
131
|
+
|
|
132
|
+
#### Streaming requires a callable consumer (a proc, lambda, or any object responding to `call`) to handle incoming data frames
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
consumer = ->(data){puts data}
|
|
136
|
+
eodhd = Eodhd.new(api_token: api_token, consumer: consumer)
|
|
137
|
+
|
|
138
|
+
eodhd.us_trade_stream('AAPL,MSFT')
|
|
139
|
+
eodhd.us_quote_stream('AAPL,MSFT')
|
|
140
|
+
eodhd.forex_stream('EURUSD')
|
|
141
|
+
eodhd.crypto_stream('BTC-USD')
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Or use the general interface
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
eodhd.stream(asset_class: 'us', symbols: 'AAPL,MSFT')
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT License. See LICENSE file for details.
|
|
153
|
+
|
|
154
|
+
## Contributing
|
|
155
|
+
|
|
156
|
+
1. Fork it ( https://github.com/thoran/eodhd.rb/fork )
|
|
157
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
158
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
159
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
160
|
+
5. Create a new pull request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'rake/testtask'
|
|
2
|
+
|
|
3
|
+
Rake::TestTask.new(:test) do |t|
|
|
4
|
+
t.libs << "lib"
|
|
5
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
|
6
|
+
t.warning = true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
task default: :test
|
|
10
|
+
|
|
11
|
+
namespace :cassettes do
|
|
12
|
+
CASSETTE_DIR = File.expand_path("test/fixtures/vcr_cassettes", __dir__)
|
|
13
|
+
|
|
14
|
+
desc "Delete all VCR cassettes"
|
|
15
|
+
task :clear do
|
|
16
|
+
if Dir.exist?(CASSETTE_DIR)
|
|
17
|
+
FileUtils.rm_rf(Dir[File.join(CASSETTE_DIR, "**", "*")])
|
|
18
|
+
puts "Cleared cassettes in #{CASSETTE_DIR}"
|
|
19
|
+
else
|
|
20
|
+
puts "Cassette dir not found: #{CASSETTE_DIR}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "Show cassette directory path"
|
|
25
|
+
task :path do
|
|
26
|
+
puts CASSETTE_DIR
|
|
27
|
+
end
|
|
28
|
+
end
|
data/eodhd.gemspec
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require_relative './lib/Eodhd/VERSION'
|
|
2
|
+
|
|
3
|
+
class Gem::Specification
|
|
4
|
+
def dependencies=(gems)
|
|
5
|
+
gems.each{|gem| add_dependency(*gem)}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def development_dependencies=(gems)
|
|
9
|
+
gems.each{|gem| add_development_dependency(*gem)}
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Gem::Specification.new do |spec|
|
|
14
|
+
spec.name = 'eodhd'
|
|
15
|
+
spec.version = Eodhd::VERSION
|
|
16
|
+
|
|
17
|
+
spec.summary = "Access the eodhd.com API with Ruby."
|
|
18
|
+
spec.description = "Access the eodhd.com API with Ruby."
|
|
19
|
+
|
|
20
|
+
spec.author = 'thoran'
|
|
21
|
+
spec.email = 'code@thoran.com'
|
|
22
|
+
spec.homepage = 'http://github.com/thoran/eodhd.rb'
|
|
23
|
+
spec.license = 'MIT'
|
|
24
|
+
|
|
25
|
+
spec.require_paths = ['lib']
|
|
26
|
+
spec.required_ruby_version = '>= 2.7'
|
|
27
|
+
|
|
28
|
+
spec.files = [
|
|
29
|
+
'CHANGELOG',
|
|
30
|
+
'eodhd.gemspec',
|
|
31
|
+
'Gemfile',
|
|
32
|
+
Dir['lib/**/*.rb'],
|
|
33
|
+
'Rakefile',
|
|
34
|
+
'README.md',
|
|
35
|
+
Dir['test/**/*.rb']
|
|
36
|
+
].flatten
|
|
37
|
+
|
|
38
|
+
spec.dependencies = %w{
|
|
39
|
+
http.rb
|
|
40
|
+
iodine
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
spec.development_dependencies = %w{
|
|
44
|
+
rake
|
|
45
|
+
minitest
|
|
46
|
+
minitest-mock
|
|
47
|
+
minitest-spec-context
|
|
48
|
+
webmock
|
|
49
|
+
vcr
|
|
50
|
+
simplecov
|
|
51
|
+
}
|
|
52
|
+
end
|
data/lib/Eodhd/Client.rb
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Eodhd/Client.rb
|
|
2
|
+
# Eodhd::Client
|
|
3
|
+
|
|
4
|
+
gem 'http.rb'
|
|
5
|
+
|
|
6
|
+
require 'http.rb'
|
|
7
|
+
require 'json'
|
|
8
|
+
|
|
9
|
+
require_relative './DefaultLogger'
|
|
10
|
+
require_relative './Error'
|
|
11
|
+
require_relative '../Hash/x_www_form_urlencode'
|
|
12
|
+
require_relative './Validations'
|
|
13
|
+
|
|
14
|
+
class Eodhd
|
|
15
|
+
class Client
|
|
16
|
+
|
|
17
|
+
API_HOST = 'eodhd.com'
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def path_prefix
|
|
21
|
+
'/api'
|
|
22
|
+
end
|
|
23
|
+
end # class << self
|
|
24
|
+
|
|
25
|
+
include Validations
|
|
26
|
+
|
|
27
|
+
# This endpoint always returns json regardless of what fmt is specified.
|
|
28
|
+
def exchanges_list
|
|
29
|
+
response = get(path: '/exchanges-list')
|
|
30
|
+
handle_response(response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def exchange_symbol_list(exchange_code:)
|
|
34
|
+
validate_arguments(exchange_code: exchange_code)
|
|
35
|
+
response = get(path: "/exchange-symbol-list/#{exchange_code}")
|
|
36
|
+
handle_response(response)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def eod_data(exchange_id:, symbol:, period:, from: nil, to: nil)
|
|
40
|
+
validate_arguments(
|
|
41
|
+
exchange_id: exchange_id,
|
|
42
|
+
symbol: symbol,
|
|
43
|
+
period: period,
|
|
44
|
+
from: from,
|
|
45
|
+
to: to
|
|
46
|
+
)
|
|
47
|
+
args = {period: period, from: from, to: to}.compact
|
|
48
|
+
response = get(path: "/eod/#{symbol}.#{exchange_id}", args: args)
|
|
49
|
+
handle_response(response)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def eod_bulk_last_day(exchange_id:, date:)
|
|
53
|
+
validate_arguments(exchange_id: exchange_id, date: date)
|
|
54
|
+
args = {date: date}
|
|
55
|
+
response = get(path: "/eod-bulk-last-day/#{exchange_id}", args: args)
|
|
56
|
+
handle_response(response)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def intraday(exchange_code:, symbol:, interval:, from: nil, to: nil)
|
|
60
|
+
validate_arguments(
|
|
61
|
+
exchange_code: exchange_code,
|
|
62
|
+
symbol: symbol,
|
|
63
|
+
interval: interval,
|
|
64
|
+
from: from,
|
|
65
|
+
to: to
|
|
66
|
+
)
|
|
67
|
+
args = {interval: interval, from: from, to: to}.compact
|
|
68
|
+
response = get(path: "/intraday/#{symbol}.#{exchange_code}", args: args)
|
|
69
|
+
handle_response(response)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fundamentals(exchange_code:, symbol:, filter: nil)
|
|
73
|
+
validate_arguments(exchange_code: exchange_code, symbol: symbol)
|
|
74
|
+
args = {filter: filter}.compact
|
|
75
|
+
response = get(path: "/fundamentals/#{symbol}.#{exchange_code}", args: args)
|
|
76
|
+
handle_response(response)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
attr_accessor\
|
|
80
|
+
:api_token,
|
|
81
|
+
:logger
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def initialize(api_token: nil, logger: nil, use_default_logger: false)
|
|
86
|
+
@api_token = api_token
|
|
87
|
+
@logger = use_default_logger ? DefaultLogger.logger : logger
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def request_string(path)
|
|
91
|
+
"https://#{API_HOST}#{self.class.path_prefix}#{path}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def log_args?(args)
|
|
95
|
+
!args.values.all?(&:nil?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def log_request(verb:, request_string:, args:)
|
|
99
|
+
log_string = "#{verb} #{request_string}"
|
|
100
|
+
if log_args?(args)
|
|
101
|
+
log_string << "?#{args.x_www_form_urlencode}"
|
|
102
|
+
end
|
|
103
|
+
@logger.info(log_string)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def log_response(code:, message:, body:)
|
|
107
|
+
log_string = "#{code}\n#{message}\n#{body}"
|
|
108
|
+
@logger.info(log_string)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def log_error(code:, message:, body:)
|
|
112
|
+
log_string = "#{code}\n#{message}\n#{body}"
|
|
113
|
+
@logger.error(log_string)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def do_request(verb:, path:, args: {})
|
|
117
|
+
api_token = args[:api_token] || @api_token
|
|
118
|
+
fmt = args[:fmt] || 'json'
|
|
119
|
+
args.merge!(api_token: api_token, fmt: fmt)
|
|
120
|
+
log_request(verb: verb, request_string: request_string(path), args: args) if use_logging?
|
|
121
|
+
HTTP.send(verb.to_s.downcase, request_string(path), args)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def get(path:, args: {})
|
|
125
|
+
do_request(verb: 'GET', path: path, args: args)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def handle_response(response)
|
|
129
|
+
if response.success?
|
|
130
|
+
parsed_body = JSON.parse(response.body)
|
|
131
|
+
log_response(code: response.code, message: response.message, body: response.body) if use_logging?
|
|
132
|
+
parsed_body
|
|
133
|
+
else
|
|
134
|
+
log_error(code: response.code, message: response.message, body: response.body) if use_logging?
|
|
135
|
+
raise Eodhd::Error.new(code: response.code, message: response.message, body: response.body)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def use_logging?
|
|
140
|
+
!@logger.nil?
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Eodhd/DefaultLogger.rb
|
|
2
|
+
# Eodhd::DefaultLogger
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'logger'
|
|
6
|
+
|
|
7
|
+
class Eodhd
|
|
8
|
+
class DefaultLogger
|
|
9
|
+
class << self
|
|
10
|
+
attr_writer :log_filepath
|
|
11
|
+
|
|
12
|
+
def default_log_filepath
|
|
13
|
+
File.join(%w{~ log eodhd log.txt})
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def log_filepath
|
|
17
|
+
File.expand_path(@log_filepath || default_log_filepath)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def log_file
|
|
21
|
+
FileUtils.mkdir_p(File.dirname(log_filepath))
|
|
22
|
+
File.open(log_filepath, File::WRONLY | File::APPEND | File::CREAT)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def logger
|
|
26
|
+
@logger ||= Logger.new(log_file, 'daily')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Eodhd/EodBulkLastDay.rb
|
|
2
|
+
# Eodhd::EodBulkLastDay
|
|
3
|
+
|
|
4
|
+
class Eodhd
|
|
5
|
+
class EodBulkLastDay
|
|
6
|
+
class << self
|
|
7
|
+
def all(client: nil, api_token: nil, exchange_code:, date:)
|
|
8
|
+
load(client: client, api_token: api_token, exchange_code: exchange_code, date:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def load(client: nil, api_token: nil, exchange_code:, date:)
|
|
14
|
+
client ||= Client.new(api_token: api_token)
|
|
15
|
+
client.eod_bulk_last_day(exchange_id: exchange_code, date: date).collect do |eod_bulk_last_day|
|
|
16
|
+
self.new(
|
|
17
|
+
code: eod_bulk_last_day['code'],
|
|
18
|
+
exchange_short_name: eod_bulk_last_day['exchange_short_name'],
|
|
19
|
+
date: eod_bulk_last_day['date'],
|
|
20
|
+
open: eod_bulk_last_day['open'],
|
|
21
|
+
high: eod_bulk_last_day['high'],
|
|
22
|
+
low: eod_bulk_last_day['low'],
|
|
23
|
+
close: eod_bulk_last_day['close'],
|
|
24
|
+
adjusted_close: eod_bulk_last_day['adjusted_close'],
|
|
25
|
+
volume: eod_bulk_last_day['volume']
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end # class << self
|
|
30
|
+
|
|
31
|
+
attr_reader\
|
|
32
|
+
:code,
|
|
33
|
+
:exchange_short_name,
|
|
34
|
+
:date,
|
|
35
|
+
:open,
|
|
36
|
+
:high,
|
|
37
|
+
:low,
|
|
38
|
+
:close,
|
|
39
|
+
:adjusted_close,
|
|
40
|
+
:volume
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def initialize(code:, exchange_short_name:, date:, open:, high:, low:, close:, adjusted_close:, volume:)
|
|
45
|
+
@code = code
|
|
46
|
+
@exchange_short_name = exchange_short_name
|
|
47
|
+
@date = date
|
|
48
|
+
@open = open
|
|
49
|
+
@high = high
|
|
50
|
+
@low = low
|
|
51
|
+
@close = close
|
|
52
|
+
@adjusted_close = adjusted_close
|
|
53
|
+
@volume = volume
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|