yf_as_dataframe 0.4.1 → 0.5.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
2
  SHA256:
3
- metadata.gz: ef9b7d363312088694d19ce68cd538579f9b3f2b93f57cf5e111f3247cd87b00
4
- data.tar.gz: 5bab2fa2aa65c4b6496025aa13f86c15d5df49deeeff2f6b60db07818d6fe1c8
3
+ metadata.gz: 56c643be2ea6cfee61ab9bc29bf43f5a73e7bd813451198ae7e7fa3176231814
4
+ data.tar.gz: 653d6e6de4e9d5990fdc98e3b6767d84660daa7fef36a5eace5dda8bf540593c
5
5
  SHA512:
6
- metadata.gz: '0019c0c2f4293f3c1b3684bff81f2e0a78eb614c505031aaa77c4ba541c88def5189ed8d96310dc6b7217f3651f37f7ea9dbb64c05009a2286d12856027368b2'
7
- data.tar.gz: 39b3d46681bbc24409315878a8722e563266553e9b81592051404ef53df6aef397c0248ab1fc724ea6edfc5f6d647e2fb89de6bf675b975aeeb592ae7244f66c
6
+ metadata.gz: 96c19e4fa417b80f6593c6e2fc167b752eda0fd9b12ec18a927c28d052fdb9c136ba2b85d15a580559b9ef187ce1a923953dd21dc9ac653626aaa6dfc29894ea
7
+ data.tar.gz: f5328c5462cd397d04f4597ae575ab4ba2082cec39243154bdbaefc031d5baa5ffb6e342e9b29926700834c4532f1d33eabbda08005e59d1ab14256ca527cb44
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.6
data/Gemfile CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ ruby "3.3.6"
6
+
5
7
  # Specify your gem's dependencies in yf_as_dataframe.gemspec
6
8
  gemspec
7
9
 
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- yf_as_dataframe (0.4.0)
4
+ yf_as_dataframe (0.4.1)
5
5
  activesupport
6
6
  httparty
7
7
  nokogiri
8
- polars-df (~> 0.12.0)
8
+ polars-df (= 0.27.1)
9
9
  tulirb
10
10
  tzinfo
11
11
  tzinfo-data
@@ -14,86 +14,203 @@ PATH
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- activesupport (7.2.2.1)
17
+ activesupport (8.1.3.1)
18
18
  base64
19
- benchmark (>= 0.3)
20
19
  bigdecimal
21
20
  concurrent-ruby (~> 1.0, >= 1.3.1)
22
21
  connection_pool (>= 2.2.5)
23
22
  drb
24
23
  i18n (>= 1.6, < 2)
24
+ json
25
25
  logger (>= 1.4.2)
26
26
  minitest (>= 5.1)
27
27
  securerandom (>= 0.3)
28
28
  tzinfo (~> 2.0, >= 2.0.5)
29
+ uri (>= 0.13.1)
30
+ addressable (2.9.0)
31
+ public_suffix (>= 2.0.2, < 8.0)
29
32
  ast (2.4.3)
30
33
  base64 (0.3.0)
31
- benchmark (0.4.1)
32
- bigdecimal (3.2.2)
33
- concurrent-ruby (1.3.5)
34
- connection_pool (2.5.3)
35
- csv (3.3.5)
34
+ bigdecimal (4.1.2)
35
+ concurrent-ruby (1.3.8)
36
+ connection_pool (3.0.2)
37
+ crack (1.0.1)
38
+ bigdecimal
39
+ rexml
40
+ csv (3.3.6)
41
+ diff-lcs (1.6.2)
36
42
  drb (2.2.3)
37
- httparty (0.23.1)
43
+ hashdiff (1.2.1)
44
+ httparty (0.24.2)
38
45
  csv
39
46
  mini_mime (>= 1.0.0)
40
47
  multi_xml (>= 0.5.2)
41
- i18n (1.14.7)
48
+ i18n (1.15.2)
42
49
  concurrent-ruby (~> 1.0)
43
- json (2.12.2)
44
- language_server-protocol (3.17.0.5)
50
+ json (2.21.2)
51
+ language_server-protocol (3.17.0.6)
45
52
  lint_roller (1.1.0)
46
53
  logger (1.7.0)
47
54
  mini_mime (1.1.5)
48
- minitest (5.25.5)
49
- multi_xml (0.7.1)
50
- bigdecimal (~> 3.1)
51
- nokogiri (1.18.8-arm64-darwin)
55
+ minitest (6.0.6)
56
+ drb (~> 2.0)
57
+ prism (~> 1.5)
58
+ multi_xml (0.9.1)
59
+ bigdecimal (>= 3.1, < 5)
60
+ nokogiri (1.19.4-aarch64-linux-gnu)
61
+ racc (~> 1.4)
62
+ nokogiri (1.19.4-aarch64-linux-musl)
63
+ racc (~> 1.4)
64
+ nokogiri (1.19.4-arm64-darwin)
65
+ racc (~> 1.4)
66
+ nokogiri (1.19.4-x86_64-darwin)
52
67
  racc (~> 1.4)
53
- parallel (1.27.0)
54
- parser (3.3.8.0)
68
+ nokogiri (1.19.4-x86_64-linux-gnu)
69
+ racc (~> 1.4)
70
+ nokogiri (1.19.4-x86_64-linux-musl)
71
+ racc (~> 1.4)
72
+ parallel (2.1.0)
73
+ parser (3.3.12.0)
55
74
  ast (~> 2.4.1)
56
75
  racc
57
- polars-df (0.12.0-arm64-darwin)
58
- bigdecimal
59
- prism (1.4.0)
76
+ polars-df (0.27.1-aarch64-linux)
77
+ polars-df (0.27.1-aarch64-linux-musl)
78
+ polars-df (0.27.1-arm64-darwin)
79
+ polars-df (0.27.1-x86_64-darwin)
80
+ polars-df (0.27.1-x86_64-linux)
81
+ polars-df (0.27.1-x86_64-linux-musl)
82
+ prism (1.9.0)
83
+ public_suffix (7.0.5)
60
84
  racc (1.8.1)
61
85
  rainbow (3.1.1)
62
- rake (13.3.0)
63
- regexp_parser (2.10.0)
64
- rubocop (1.77.0)
65
- json (~> 2.3)
86
+ rake (13.4.2)
87
+ regexp_parser (2.12.0)
88
+ rexml (3.4.4)
89
+ rspec (3.13.2)
90
+ rspec-core (~> 3.13.0)
91
+ rspec-expectations (~> 3.13.0)
92
+ rspec-mocks (~> 3.13.0)
93
+ rspec-core (3.13.6)
94
+ rspec-support (~> 3.13.0)
95
+ rspec-expectations (3.13.5)
96
+ diff-lcs (>= 1.2.0, < 2.0)
97
+ rspec-support (~> 3.13.0)
98
+ rspec-mocks (3.13.8)
99
+ diff-lcs (>= 1.2.0, < 2.0)
100
+ rspec-support (~> 3.13.0)
101
+ rspec-support (3.13.7)
102
+ rubocop (1.90.0)
103
+ json (>= 2.3)
66
104
  language_server-protocol (~> 3.17.0.2)
67
105
  lint_roller (~> 1.1.0)
68
- parallel (~> 1.10)
106
+ parallel (>= 1.10)
69
107
  parser (>= 3.3.0.2)
70
108
  rainbow (>= 2.2.2, < 4.0)
71
109
  regexp_parser (>= 2.9.3, < 3.0)
72
- rubocop-ast (>= 1.45.1, < 2.0)
110
+ rubocop-ast (>= 1.49.0, < 2.0)
73
111
  ruby-progressbar (~> 1.7)
74
112
  unicode-display_width (>= 2.4.0, < 4.0)
75
- rubocop-ast (1.45.1)
113
+ rubocop-ast (1.50.0)
76
114
  parser (>= 3.3.7.2)
77
- prism (~> 1.4)
115
+ prism (~> 1.7)
78
116
  ruby-progressbar (1.13.0)
79
117
  securerandom (0.4.1)
80
118
  tulirb (1.0.0)
81
119
  tzinfo (2.0.6)
82
120
  concurrent-ruby (~> 1.0)
83
- tzinfo-data (1.2025.2)
121
+ tzinfo-data (1.2026.3)
84
122
  tzinfo (>= 1.0.0)
85
- unicode-display_width (3.1.4)
86
- unicode-emoji (~> 4.0, >= 4.0.4)
87
- unicode-emoji (4.0.4)
88
- zache (0.15.0)
123
+ unicode-display_width (3.2.0)
124
+ unicode-emoji (~> 4.1)
125
+ unicode-emoji (4.2.0)
126
+ uri (1.1.1)
127
+ vcr (6.4.0)
128
+ webmock (3.26.3)
129
+ addressable (>= 2.8.0)
130
+ crack (>= 0.3.2)
131
+ hashdiff (>= 0.4.0, < 2.0.0)
132
+ zache (0.15.2)
89
133
 
90
134
  PLATFORMS
91
- arm64-darwin-23
135
+ aarch64-linux
136
+ aarch64-linux-gnu
137
+ aarch64-linux-musl
138
+ arm64-darwin
139
+ x86_64-darwin
140
+ x86_64-linux-gnu
141
+ x86_64-linux-musl
92
142
 
93
143
  DEPENDENCIES
94
144
  rake (~> 13.0)
145
+ rspec (~> 3.13)
95
146
  rubocop (~> 1.21)
147
+ vcr (~> 6.2)
148
+ webmock (~> 3.23)
96
149
  yf_as_dataframe!
97
150
 
151
+ CHECKSUMS
152
+ activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376
153
+ addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af
154
+ ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
155
+ base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
156
+ bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
157
+ bundler (4.0.19) sha256=b48056e4c77fb3853cc47775b5447ede44aaa4f53c32f168014ab4fe86587d47
158
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
159
+ connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
160
+ crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
161
+ csv (3.3.6) sha256=aba61e7e507a66f03d45cb1f3c4b6359861c3504038b422962875dce099e4456
162
+ diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
163
+ drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
164
+ hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
165
+ httparty (0.24.2) sha256=8fca6a54aa0c4aa4303a0fd33e5e2156175d6a5334f714263b458abd7fda9c38
166
+ i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
167
+ json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
168
+ language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
169
+ lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
170
+ logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
171
+ mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
172
+ minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
173
+ multi_xml (0.9.1) sha256=7ce766b59c17241ed62976caeae1fae9b2431b263398c35396239a68c4a64e57
174
+ nokogiri (1.19.4-aarch64-linux-gnu) sha256=1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f
175
+ nokogiri (1.19.4-aarch64-linux-musl) sha256=35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af
176
+ nokogiri (1.19.4-arm64-darwin) sha256=a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527
177
+ nokogiri (1.19.4-x86_64-darwin) sha256=7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551
178
+ nokogiri (1.19.4-x86_64-linux-gnu) sha256=379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a
179
+ nokogiri (1.19.4-x86_64-linux-musl) sha256=17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b
180
+ parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
181
+ parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
182
+ polars-df (0.27.1-aarch64-linux) sha256=d85cedc6574fc66b1084a72380243ee73e7f8e6cdcf098c08c929dfafb429c83
183
+ polars-df (0.27.1-aarch64-linux-musl) sha256=e70f9b19453707be8e951b87b6a119e50e4cb8461e550f13568e655a52ae0c81
184
+ polars-df (0.27.1-arm64-darwin) sha256=e059d3a0f53e9d3a3484396c58ef42801d463becc4af632adf78a606d1117f97
185
+ polars-df (0.27.1-x86_64-darwin) sha256=cf8389bd81ec0e3ed7bbf8aa7078de88bcb72ebae445cbc52d4619ade6dfb181
186
+ polars-df (0.27.1-x86_64-linux) sha256=3cb35fe50bfd783845976e6bc567a560d547dbded8a96887a2d5a658d01396e2
187
+ polars-df (0.27.1-x86_64-linux-musl) sha256=6ad62495e0385df6305368bdd45d61b1baea2193f269737fbe03ed4fe4236595
188
+ prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
189
+ public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
190
+ racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
191
+ rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
192
+ rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
193
+ regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
194
+ rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
195
+ rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
196
+ rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
197
+ rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
198
+ rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
199
+ rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
200
+ rubocop (1.90.0) sha256=9eb4c065b5c5154e4ef554c547972f3905a9eb6b53e657e580b6796b54bf8242
201
+ rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
202
+ ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
203
+ securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
204
+ tulirb (1.0.0) sha256=5f2f9889976014581801106f3ef21ff63ab94a9c1d291557e7dfdf8871c73379
205
+ tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
206
+ tzinfo-data (1.2026.3) sha256=478fbc5356f13c1004cf8372b1336f3dad4055c96340fc4c881a3738da8cf7f9
207
+ unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
208
+ unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
209
+ uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
210
+ vcr (6.4.0) sha256=077ac92cc16efc5904eb90492a18153b5e6ca5398046d8a249a7c96a9ea24ae6
211
+ webmock (3.26.3) sha256=77c612816bdcb2fc43db0e42fd98b19be7d4770fd708816fdde65a006664ddec
212
+ yf_as_dataframe (0.4.1)
213
+ zache (0.15.2) sha256=0192d9fe0081e6c83ec051630f3347d8692a7309094066b9aadaa481175707bd
214
+
98
215
  BUNDLED WITH
99
- 2.6.9
216
+ 4.0.19
data/README.md CHANGED
@@ -30,6 +30,11 @@ Yahoo! finance API is intended for personal use only.**
30
30
 
31
31
  This package provides for pulling data from Yahoo!'s unofficial API, and providing that data using using [Polars](https://github.com/ankane/ruby-polars?tab=readme-ov-file) dataframes in ruby. Data in those dataframes can then be easily post-processed using technical indicators provided by [Tulip](https://tulipindicators.org/) via [Tulirb's](https://www.rubydoc.info/github/ozone4real/tulirb/main/Tulirb) ruby bindings, and visualized using [Vega](https://github.com/ankane/vega-ruby).
32
32
 
33
+ ## Requirements
34
+
35
+ - **Ruby**: >= 3.3
36
+ - **polars-df**: 0.27.1
37
+
33
38
  ### Quick Start: The Ticker module
34
39
 
35
40
  The `Ticker` class, which allows you to access ticker data from Yahoo!'s unofficial API:
@@ -329,6 +334,65 @@ Or install it yourself as:
329
334
 
330
335
  ---
331
336
 
337
+ ## Testing
338
+
339
+ This gem includes a comprehensive test suite using RSpec. Tests use WebMock and VCR to mock HTTP requests, ensuring tests run without hitting the live Yahoo Finance API.
340
+
341
+ ### Running Tests Locally
342
+
343
+ To run the full test suite:
344
+
345
+ ```bash
346
+ bundle install
347
+ bundle exec rspec
348
+ ```
349
+
350
+ To run tests with documentation format:
351
+
352
+ ```bash
353
+ bundle exec rspec --format documentation
354
+ ```
355
+
356
+ To run specific test files:
357
+
358
+ ```bash
359
+ bundle exec rspec spec/yf_as_dataframe/ticker_spec.rb
360
+ bundle exec rspec spec/yf_as_dataframe/yf_connection_spec.rb
361
+ ```
362
+
363
+ ### Running Rake Tasks
364
+
365
+ The default rake task runs both tests and RuboCop:
366
+
367
+ ```bash
368
+ bundle exec rake
369
+ ```
370
+
371
+ Run only tests:
372
+
373
+ ```bash
374
+ bundle exec rake spec
375
+ ```
376
+
377
+ Run only RuboCop:
378
+
379
+ ```bash
380
+ bundle exec rake rubocop
381
+ ```
382
+
383
+ ### Test Coverage
384
+
385
+ The test suite covers:
386
+
387
+ - **Ticker#history**: Historical price data retrieval with mocked HTTP responses
388
+ - **YfConnection**: User-Agent headers, cookie management, circuit breaker, and request throttling
389
+ - **Technical Indicators**: SMA, EMA, RSI, OBV, and other price technical indicators
390
+ - **Error Handling**: Invalid symbols, network failures, and malformed responses
391
+
392
+ Tests are designed to run offline using fixtures, avoiding live network requests and ensuring consistent, fast test execution.
393
+
394
+ ---
395
+
332
396
  ## Development
333
397
 
334
398
  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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
data/Rakefile CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rubocop/rake_task"
5
+ require "rspec/core/rake_task"
5
6
 
6
7
  RuboCop::RakeTask.new
8
+ RSpec::Core::RakeTask.new(:spec)
7
9
 
8
- task default: :rubocop
10
+ task default: %i[spec rubocop]
@@ -81,8 +81,8 @@ class YfAsDataframe
81
81
  end
82
82
  cmd << url
83
83
 
84
- puts "DEBUG: curl-impersonate command: #{cmd.join(' ')}"
85
- puts "DEBUG: curl-impersonate timeout: #{timeout} seconds"
84
+ # puts "DEBUG: curl-impersonate command: #{cmd.join(' ')}"
85
+ # puts "DEBUG: curl-impersonate timeout: #{timeout} seconds"
86
86
 
87
87
  begin
88
88
  stdout_str = ''
@@ -95,7 +95,7 @@ class YfAsDataframe
95
95
  monitor = Thread.new do
96
96
  sleep(timeout + 10)
97
97
  unless done
98
- puts "DEBUG: Killing curl-impersonate PID \\#{pid} after timeout"
98
+ # puts "DEBUG: Killing curl-impersonate PID \\#{pid} after timeout"
99
99
  Process.kill('TERM', pid) rescue nil
100
100
  sleep(1)
101
101
  Process.kill('KILL', pid) rescue nil if wait_thr.alive?
@@ -107,23 +107,23 @@ class YfAsDataframe
107
107
  done = true
108
108
  monitor.kill
109
109
  end
110
- puts "DEBUG: curl-impersonate stdout: #{stdout_str[0..200]}..." if stdout_str && !stdout_str.empty?
111
- puts "DEBUG: curl-impersonate stderr: #{stderr_str}" if stderr_str && !stderr_str.empty?
112
- puts "DEBUG: curl-impersonate status: #{status.exitstatus}"
113
- if status.success?
114
- response = OpenStruct.new
115
- response.body = stdout_str
116
- response.code = 200
117
- response.define_singleton_method(:success?) { true }
118
- response.parsed_response = parse_json_if_possible(stdout_str)
119
- response
120
- else
110
+ # puts "DEBUG: curl-impersonate stdout: #{stdout_str[0..200]}..." if stdout_str && !stdout_str.empty?
111
+ # puts "DEBUG: curl-impersonate stderr: #{stderr_str}" if stderr_str && !stderr_str.empty?
112
+ # puts "DEBUG: curl-impersonate status: #{status.exitstatus}"
113
+ if status.success?
114
+ response = OpenStruct.new
115
+ response.body = stdout_str
116
+ response.code = 200
117
+ response.define_singleton_method(:success?) { true }
118
+ response.parsed_response = parse_json_if_possible(stdout_str)
119
+ response
120
+ else
121
+ # puts "DEBUG: curl-impersonate failed with error: \\#{error_message}"
121
122
  error_message = "curl failed with code \\#{status.exitstatus}: \\#{stderr_str}"
122
- puts "DEBUG: curl-impersonate failed with error: \\#{error_message}"
123
123
  nil
124
124
  end
125
125
  rescue => e
126
- puts "DEBUG: curl-impersonate exception: \\#{e.message}"
126
+ # puts "DEBUG: curl-impersonate exception: \\#{e.message}"
127
127
  nil
128
128
  end
129
129
  end
@@ -143,7 +143,7 @@ class YfAsDataframe
143
143
 
144
144
  df = Polars::DataFrame.new(df)
145
145
  timestamps.map{|ts| Time.at(ts).utc.to_date.to_s }.each do |t|
146
- puts t
146
+ # puts t
147
147
  df.replace(t, Polars::Series.new(df[t].cast(Polars::String)))
148
148
  end
149
149
  df
@@ -161,7 +161,7 @@ class YfAsDataframe
161
161
  statement = _create_financials_table(nam, timescale)
162
162
  return statement unless statement.nil?
163
163
  rescue Yfin::YfinDataException => e
164
- Logger.new(STDOUT).error {"#{@symbol}: Failed to create #{nam} financials table for reason: #{e}"}
164
+ # Logger.new(STDOUT).error {"#{@symbol}: Failed to create #{nam} financials table for reason: #{e}"}
165
165
  end
166
166
  Polars::DataFrame.new()
167
167
  end
@@ -100,7 +100,7 @@ class YfAsDataframe
100
100
  result = get_raw_json(QUOTE_SUMMARY_URL + "/#{symbol}", user_agent_headers=user_agent_headers, params=params_dict)
101
101
  # Rails.logger.info { "#{__FILE__}:#{__LINE__} result = #{result.inspect}" }
102
102
  rescue Exception => e
103
- Logger.new(STDOUT).error("ERROR: #{e.message}")
103
+ # Logger.new(STDOUT).error("ERROR: #{e.message}")
104
104
  return nil
105
105
  end
106
106
  return result
@@ -136,7 +136,7 @@ class YfAsDataframe
136
136
 
137
137
  def _parse_result(result)
138
138
  data = result.parsed_response['quoteSummary']['result'].first #.dig('quoteSummary', 'result', 0)
139
- Logger.new(STDOUT).info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
139
+ # Logger.new(STDOUT).info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
140
140
  _parse_institution_ownership(data['institutionOwnership'])
141
141
  _parse_fund_ownership(data['fundOwnership'])
142
142
  _parse_major_holders_breakdown(data['majorHoldersBreakdown'])
@@ -62,19 +62,20 @@ class YfAsDataframe
62
62
  # """
63
63
  logger = Logger.new(STDOUT)
64
64
 
65
+ # YfAsDataframe::Utils.print_once("yfinance: download(show_errors=#{show_errors}) argument is deprecated and will be removed in future version. Do this instead: logging.getLogger('yfinance').setLevel(logging.ERROR)")
66
+
65
67
  if show_errors
66
- YfAsDataframe::Utils.print_once("yfinance: download(show_errors=#{show_errors}) argument is deprecated and will be removed in future version. Do this instead: logging.getLogger('yfinance').setLevel(logging.ERROR)")
67
- logger.level = Logger::ERROR
68
+ # YfAsDataframe::Utils.print_once("yfinance: download(show_errors=#{show_errors}) argument is deprecated and will be removed in future version. Do this instead to suppress error messages: logging.getLogger('yfinance').setLevel(logging.CRITICAL)")
69
+ # logger.level = Logger::CRITICAL
68
70
  else
69
- YfAsDataframe::Utils.print_once("yfinance: download(show_errors=#{show_errors}) argument is deprecated and will be removed in future version. Do this instead to suppress error messages: logging.getLogger('yfinance').setLevel(logging.CRITICAL)")
70
- logger.level = Logger::CRITICAL
71
+ # logger.level = Logger::CRITICAL
71
72
  end
72
73
 
73
- if logger.debug?
74
- threads = false if threads
75
- logger.debug('Disabling multithreading because DEBUG logging enabled')
76
- progress = false if progress
77
- end
74
+ # if logger.debug?
75
+ # threads = false if threads
76
+ # logger.debug('Disabling multithreading because DEBUG logging enabled')
77
+ # progress = false if progress
78
+ # end
78
79
 
79
80
  ignore_tz = interval[1..-1].match?(/[mh]/) ? false : true if ignore_tz.nil?
80
81
 
@@ -35,7 +35,7 @@ class YfAsDataframe
35
35
  def history(period: "1mo", interval: "1d", start: nil, fin: nil, prepost: false,
36
36
  actions: true, auto_adjust: true, back_adjust: false, repair: false, keepna: false,
37
37
  rounding: false, raise_errors: false, returns: false)
38
- logger = Logger.new(STDOUT) # Replace Rails.logger with standard Ruby logger
38
+ # logger = Logger.new(STDOUT) # Replace Rails.logger with standard Ruby logger
39
39
  start_user = start
40
40
  # Rails.logger.info { "#{__FILE__}:#{__LINE__} here" }
41
41
  end_user = fin || Time.now
@@ -75,7 +75,7 @@ class YfAsDataframe
75
75
  if raise_errors
76
76
  raise Exception.new("#{ticker}: #{err_msg}")
77
77
  else
78
- logger.error("#{ticker}: #{err_msg}")
78
+ # logger.error("#{ticker}: #{err_msg}")
79
79
  end
80
80
  if @reconstruct_start_interval && @reconstruct_start_interval == interval
81
81
  @reconstruct_start_interval = nil
@@ -572,7 +572,7 @@ class YfAsDataframe
572
572
  if raise_errors
573
573
  raise Exception.new("#{@ticker}: #{err_msg}")
574
574
  else
575
- Logger.new(STDOUT).error("#{@ticker}: #{err_msg}")
575
+ # Logger.new(STDOUT).error("#{@ticker}: #{err_msg}")
576
576
  end
577
577
  return YfAsDataframe::Utils.empty_df
578
578
  end
@@ -617,8 +617,8 @@ class YfAsDataframe
617
617
  def _get_data(ticker, params, fin, raise_errors)
618
618
  url = "https://query2.finance.yahoo.com/v8/finance/chart/#{CGI.escape ticker}"
619
619
  # url = "https://query1.finance.yahoo.com/v7/finance/download/#{ticker}" ... Deprecated
620
- logger = Logger.new(STDOUT)
621
- logger.info { "#{__FILE__}:#{__LINE__} url = #{url}" }
620
+ # logger = Logger.new(STDOUT)
621
+ # logger.info { "#{__FILE__}:#{__LINE__} url = #{url}" }
622
622
  data = nil
623
623
  # get_fn = @data.method(:get)
624
624
 
@@ -631,9 +631,9 @@ class YfAsDataframe
631
631
  end
632
632
 
633
633
  begin
634
- logger.info { "#{__FILE__}:#{__LINE__} url = #{url}, params = #{params.inspect}" }
634
+ # logger.info { "#{__FILE__}:#{__LINE__} url = #{url}, params = #{params.inspect}" }
635
635
  data = get(url, nil, params).parsed_response
636
- logger.info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
636
+ # logger.info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
637
637
 
638
638
  # Validate response before processing
639
639
  unless validate_yahoo_response(data)
@@ -647,10 +647,10 @@ class YfAsDataframe
647
647
 
648
648
  # Use standard Ruby Hash
649
649
  data = data.is_a?(Hash) ? data : JSON.parse(data.to_s) rescue data
650
- logger.info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
650
+ # logger.info { "#{__FILE__}:#{__LINE__} data = #{data.inspect}" }
651
651
  rescue Exception => e
652
- logger.error { "#{__FILE__}:#{__LINE__} Exception caught: #{e.message}" }
653
- logger.error { "#{__FILE__}:#{__LINE__} Exception backtrace: #{e.backtrace.first(5).join("\n")}" }
652
+ # logger.error { "#{__FILE__}:#{__LINE__} Exception caught: #{e.message}" }
653
+ # logger.error { "#{__FILE__}:#{__LINE__} Exception backtrace: #{e.backtrace.first(5).join("\n")}" }
654
654
  raise if raise_errors
655
655
  end
656
656
 
@@ -1085,7 +1085,7 @@ class YfAsDataframe
1085
1085
  # quotes.sort_index!(inplace: true)
1086
1086
 
1087
1087
  if interval.downcase == "30m"
1088
- logger.debug("#{ticker}: resampling 30m OHLC from 15m")
1088
+ # logger.debug("#{ticker}: resampling 30m OHLC from 15m")
1089
1089
  quotes2 = quotes.resample('30T')
1090
1090
  quotes = Polars::DataFrame.new(index: quotes2.last.index, data: {
1091
1091
  'Open' => quotes2['Open'].first,
@@ -395,7 +395,7 @@ class YfAsDataframe
395
395
  end
396
396
 
397
397
  def rsi(df, window: 20)
398
- return nil if w == 1
398
+ return nil if window == 1
399
399
  inputs = ['Adj Close'].map{|col| Polars::Series.new(df[col]).to_a}
400
400
  output = Tulirb.rsi(inputs, period: window).first
401
401
  Polars::Series.new("#{window}-day RSI", [nil]*(df.rows.length - output.length)+output) #)
@@ -130,7 +130,7 @@ class YfAsDataframe
130
130
  result = get_raw_json(QUOTE_SUMMARY_URL + "/#{symbol}", user_agent_headers=user_agent_headers, params=params_dict)
131
131
  # Rails.logger.info { "#{__FILE__}:#{__LINE__} result = #{result.inspect}" }
132
132
  rescue Exception => e
133
- Logger.new(STDOUT).error("ERROR: #{e.message}")
133
+ # Logger.new(STDOUT).error("ERROR: #{e.message}")
134
134
  return nil
135
135
  end
136
136
  return result
@@ -47,7 +47,7 @@ class YfAsDataframe
47
47
  def symbol; @ticker; end
48
48
 
49
49
  def shares_full(start: nil, fin: nil)
50
- logger = Logger.new(STDOUT)
50
+ # logger = Logger.new(STDOUT)
51
51
 
52
52
  # Rails.logger.info { "#{__FILE__}:#{__LINE__} start = #{start.inspect}, fin = #{fin.inspect}" }
53
53
 
@@ -71,7 +71,7 @@ class YfAsDataframe
71
71
  start ||= Time.new(fin.year, fin.month, fin.day) - 548*24*60*60
72
72
 
73
73
  if start >= fin
74
- logger.error("Start date (#{start}) must be before end (#{fin})")
74
+ # logger.error("Start date (#{start}) must be before end (#{fin})")
75
75
  return nil
76
76
  end
77
77
 
@@ -81,13 +81,13 @@ class YfAsDataframe
81
81
  begin
82
82
  json_data = get(shares_url).parsed_response
83
83
  rescue #_json.JSONDecodeError, requests.exceptions.RequestException
84
- logger.error("#{@ticker}: Yahoo web request for share count failed")
84
+ # logger.error("#{@ticker}: Yahoo web request for share count failed")
85
85
  return nil
86
86
  end
87
87
 
88
88
  fail = json_data["finance"]["error"]["code"] == "Bad Request" rescue false
89
89
  if fail
90
- logger.error("#{@ticker}: Yahoo web request for share count failed")
90
+ # logger.error("#{@ticker}: Yahoo web request for share count failed")
91
91
  return nil
92
92
  end
93
93
 
@@ -153,7 +153,7 @@ class YfAsDataframe
153
153
  # """
154
154
  return @earnings_dates[limit] if @earnings_dates && @earnings_dates[limit]
155
155
 
156
- logger = Logger.new(STDOUT)
156
+ # logger = Logger.new(STDOUT)
157
157
 
158
158
  page_size = [limit, 100].min # YF caps at 100, don't go higher
159
159
  page_offset = 0
@@ -379,7 +379,7 @@ def attributes(obj)
379
379
  end
380
380
 
381
381
  def print_once(msg)
382
- puts msg
382
+ # puts msg
383
383
  end
384
384
 
385
385
  def get_yf_logger
@@ -392,7 +392,7 @@ def setup_debug_formatting
392
392
 
393
393
  return unless logger.level == Logger::DEBUG
394
394
 
395
- logger.formatter = MultiLineFormatter.new('%(levelname)-8s %(message)s')
395
+ # logger.formatter = MultiLineFormatter.new('%(levelname)-8s %(message)s')
396
396
  end
397
397
 
398
398
  def enable_debug_mode
@@ -1,3 +1,3 @@
1
1
  class YfAsDataframe
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -12,12 +12,12 @@ class YfAsDataframe
12
12
  # Override get method to use curl-impersonate by default
13
13
  def get(url, headers=nil, params=nil)
14
14
  # Debug output
15
- puts "DEBUG: curl_impersonate_enabled = #{CurlImpersonateIntegration.curl_impersonate_enabled}"
16
- puts "DEBUG: curl_impersonate_fallback = #{CurlImpersonateIntegration.curl_impersonate_fallback}"
15
+ # puts "DEBUG: curl_impersonate_enabled = #{CurlImpersonateIntegration.curl_impersonate_enabled}"
16
+ # puts "DEBUG: curl_impersonate_fallback = #{CurlImpersonateIntegration.curl_impersonate_fallback}"
17
17
 
18
18
  # Try curl-impersonate first if enabled
19
19
  if CurlImpersonateIntegration.curl_impersonate_enabled
20
- puts "DEBUG: Trying curl-impersonate..."
20
+ # puts "DEBUG: Trying curl-impersonate..."
21
21
  begin
22
22
  # Prepare headers and params as in original method
23
23
  headers ||= {}
@@ -55,28 +55,27 @@ class YfAsDataframe
55
55
  retries: CurlImpersonateIntegration.curl_impersonate_retries
56
56
  )
57
57
 
58
- if response && response.success?
59
- puts "DEBUG: curl-impersonate succeeded"
58
+ if response && !response.empty?
59
+ # puts "DEBUG: curl-impersonate succeeded"
60
60
  return response
61
61
  else
62
- puts "DEBUG: curl-impersonate returned nil or failed"
62
+ # puts "DEBUG: curl-impersonate returned nil or failed"
63
63
  end
64
64
  rescue => e
65
- # Log error but continue to fallback
66
- puts "DEBUG: curl-impersonate exception: #{e.message}"
65
+ # puts "DEBUG: curl-impersonate exception: #{e.message}"
67
66
  # warn "curl-impersonate request failed: #{e.message}" if $VERBOSE
68
67
  end
69
68
  else
70
- puts "DEBUG: curl-impersonate is disabled, skipping to fallback"
69
+ # puts "DEBUG: curl-impersonate is disabled, skipping to fallback"
71
70
  end
72
71
 
73
72
  # Fallback to original HTTParty method
74
73
  if CurlImpersonateIntegration.curl_impersonate_fallback
75
- puts "DEBUG: Using HTTParty fallback"
76
- get_original(url, headers, params)
74
+ # puts "DEBUG: Using HTTParty fallback"
75
+ return HTTParty.get(url, headers: headers).body
77
76
  else
78
- puts "DEBUG: Fallback is disabled, but forcing fallback anyway"
79
- get_original(url, headers, params)
77
+ # puts "DEBUG: Fallback is disabled, but forcing fallback anyway"
78
+ return HTTParty.get(url, headers: headers).body
80
79
  end
81
80
  end
82
81
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yf_as_dataframe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bill McKinnon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-07-07 00:00:00.000000000 Z
11
+ date: 2026-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tzinfo
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: polars-df
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 0.12.0
47
+ version: 0.27.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 0.12.0
54
+ version: 0.27.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: zache
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +122,76 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.13'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.13'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.23'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.23'
153
+ - !ruby/object:Gem::Dependency
154
+ name: vcr
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '6.2'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '6.2'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rake
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '13.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '13.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rubocop
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.21'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1.21'
125
195
  description: Download market data from Yahoo! Finance's API
126
196
  email:
127
197
  - bill@bmck.org
@@ -129,7 +199,9 @@ executables: []
129
199
  extensions: []
130
200
  extra_rdoc_files: []
131
201
  files:
202
+ - ".rspec"
132
203
  - ".rubocop.yml"
204
+ - ".ruby-version"
133
205
  - CHANGELOG.rst
134
206
  - CODE_OF_CONDUCT.md
135
207
  - Gemfile
@@ -156,7 +228,6 @@ files:
156
228
  - lib/yf_as_dataframe/yf_connection.rb
157
229
  - lib/yf_as_dataframe/yf_connection_minimal_patch.rb
158
230
  - lib/yf_as_dataframe/yfinance_exception.rb
159
- - smoke_test.rb
160
231
  homepage: https://www.github.com/bmck/yf_as_dataframe
161
232
  licenses:
162
233
  - MIT
@@ -172,7 +243,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
172
243
  requirements:
173
244
  - - ">="
174
245
  - !ruby/object:Gem::Version
175
- version: 3.0.0
246
+ version: '3.3'
176
247
  required_rubygems_version: !ruby/object:Gem::Requirement
177
248
  requirements:
178
249
  - - ">="
data/smoke_test.rb DELETED
@@ -1,64 +0,0 @@
1
- # require "bundler/setup"
2
- require "yf_as_dataframe"
3
-
4
- def print_section(title)
5
- puts "\n=== #{title} ==="
6
- end
7
-
8
- begin
9
- print_section("Ticker Creation")
10
- msft = YfAsDataframe::Ticker.new("MSFT")
11
- puts "Ticker created: #{msft.ticker}"
12
-
13
- print_section("Price History")
14
- hist = msft.history(period: "1mo")
15
- puts "History DataFrame shape: #{hist.shape}" if hist
16
-
17
- print_section("Meta Information")
18
- meta = msft.history_metadata
19
- puts "Meta: #{meta.inspect}"
20
-
21
- print_section("Actions")
22
- puts "Dividends: #{msft.dividends.inspect}"
23
- puts "Splits: #{msft.splits.inspect}"
24
-
25
- print_section("Share Count")
26
- shares = msft.shares_full(start: "2022-01-01", fin: nil)
27
- puts "Shares DataFrame shape: #{shares.shape}" if shares
28
-
29
- print_section("Financials")
30
- puts "Income Statement: #{msft.income_stmt.inspect}"
31
- puts "Balance Sheet: #{msft.balance_sheet.inspect}"
32
- puts "Cash Flow: #{msft.cashflow.inspect}"
33
-
34
- print_section("Holders")
35
- puts "Major Holders: #{msft.major_holders.inspect}"
36
- puts "Institutional Holders: #{msft.institutional_holders.inspect}"
37
-
38
- print_section("Recommendations")
39
- puts "Recommendations: #{msft.recommendations.inspect}"
40
-
41
- print_section("Earnings Dates")
42
- puts "Earnings Dates: #{msft.earnings_dates.inspect}"
43
-
44
- print_section("ISIN")
45
- puts "ISIN: #{msft.isin.inspect}"
46
-
47
- print_section("Options")
48
- puts "Options: #{msft.options.inspect}"
49
-
50
- print_section("News")
51
- puts "News: #{msft.news.inspect}"
52
-
53
- print_section("Technical Indicator Example")
54
- if hist
55
- ad = YfAsDataframe.ad(hist)
56
- puts "AD indicator: #{ad.inspect}"
57
- end
58
-
59
- puts "\nAll tests completed successfully!"
60
-
61
- rescue => e
62
- puts "\nTest failed: #{e.class} - #{e.message}"
63
- puts e.backtrace.first(10)
64
- end