9
9
10
10
"""
11
11
import pandas as pd
12
- import numpy as np
13
12
14
13
from .context import yfinance as yf
15
14
from .context import session_gbl
15
+ from yfinance .exceptions import YFNotImplementedError
16
+
16
17
17
18
import unittest
18
19
import requests_cache
19
-
20
+ from typing import Union , Any
21
+
22
+ ticker_attributes = (
23
+ ("major_holders" , pd .DataFrame ),
24
+ ("institutional_holders" , pd .DataFrame ),
25
+ ("mutualfund_holders" , pd .DataFrame ),
26
+ ("splits" , pd .Series ),
27
+ ("actions" , pd .DataFrame ),
28
+ ("shares" , pd .DataFrame ),
29
+ ("info" , dict ),
30
+ ("calendar" , pd .DataFrame ),
31
+ ("recommendations" , Union [pd .DataFrame , dict ]),
32
+ ("earnings" , pd .DataFrame ),
33
+ ("quarterly_earnings" , pd .DataFrame ),
34
+ ("recommendations_summary" , Union [pd .DataFrame , dict ]),
35
+ ("quarterly_cashflow" , pd .DataFrame ),
36
+ ("cashflow" , pd .DataFrame ),
37
+ ("quarterly_balance_sheet" , pd .DataFrame ),
38
+ ("balance_sheet" , pd .DataFrame ),
39
+ ("quarterly_income_stmt" , pd .DataFrame ),
40
+ ("income_stmt" , pd .DataFrame ),
41
+ ("analyst_price_target" , pd .DataFrame ),
42
+ ("revenue_forecasts" , pd .DataFrame ),
43
+ ("sustainability" , pd .DataFrame ),
44
+ ("options" , tuple ),
45
+ ("news" , Any ),
46
+ ("earnings_trend" , pd .DataFrame ),
47
+ ("earnings_dates" , pd .DataFrame ),
48
+ ("earnings_forecasts" , pd .DataFrame ),
49
+ )
50
+
51
+ def assert_attribute_type (testClass : unittest .TestCase , instance , attribute_name , expected_type ):
52
+ try :
53
+ attribute = getattr (instance , attribute_name )
54
+ if attribute is not None and expected_type is not Any :
55
+ testClass .assertEqual (type (attribute ), expected_type )
56
+ except Exception :
57
+ testClass .assertRaises (
58
+ YFNotImplementedError , lambda : getattr (instance , attribute_name )
59
+ )
20
60
21
61
class TestTicker (unittest .TestCase ):
22
62
session = None
@@ -61,39 +101,9 @@ def test_badTicker(self):
61
101
for k in dat .fast_info :
62
102
dat .fast_info [k ]
63
103
64
- dat .isin
65
- dat .major_holders
66
- dat .institutional_holders
67
- dat .mutualfund_holders
68
- dat .dividends
69
- dat .splits
70
- dat .actions
71
- dat .get_shares_full ()
72
- dat .options
73
- dat .news
74
- dat .earnings_dates
75
-
76
- dat .income_stmt
77
- dat .quarterly_income_stmt
78
- dat .balance_sheet
79
- dat .quarterly_balance_sheet
80
- dat .cashflow
81
- dat .quarterly_cashflow
82
-
83
- # These haven't been ported Yahoo API
84
- # dat.shares
85
- # dat.info
86
- # dat.calendar
87
- # dat.recommendations
88
- # dat.earnings
89
- # dat.quarterly_earnings
90
- # dat.recommendations_summary
91
- # dat.analyst_price_target
92
- # dat.revenue_forecasts
93
- # dat.sustainability
94
- # dat.earnings_trend
95
- # dat.earnings_forecasts
96
-
104
+ for attribute_name , attribute_type in ticker_attributes :
105
+ assert_attribute_type (self , dat , attribute_name , attribute_type )
106
+
97
107
def test_goodTicker (self ):
98
108
# that yfinance works when full api is called on same instance of ticker
99
109
@@ -113,61 +123,20 @@ def test_goodTicker(self):
113
123
for k in dat .fast_info :
114
124
dat .fast_info [k ]
115
125
116
- dat .isin
117
- dat .major_holders
118
- dat .institutional_holders
119
- dat .mutualfund_holders
120
- dat .dividends
121
- dat .splits
122
- dat .actions
123
- dat .get_shares_full ()
124
- dat .options
125
- dat .news
126
- dat .earnings_dates
127
-
128
- dat .income_stmt
129
- dat .quarterly_income_stmt
130
- dat .balance_sheet
131
- dat .quarterly_balance_sheet
132
- dat .cashflow
133
- dat .quarterly_cashflow
134
-
135
- # These require decryption which is broken:
136
- # dat.shares
137
- # dat.info
138
- # dat.calendar
139
- # dat.recommendations
140
- # dat.earnings
141
- # dat.quarterly_earnings
142
- # dat.recommendations_summary
143
- # dat.analyst_price_target
144
- # dat.revenue_forecasts
145
- # dat.sustainability
146
- # dat.earnings_trend
147
- # dat.earnings_forecasts
148
-
126
+ for attribute_name , attribute_type in ticker_attributes :
127
+ assert_attribute_type (self , dat , attribute_name , attribute_type )
128
+
129
+ #TODO:: Refactor with `assert_attribute` once proxy is accepted as a parameter of `Ticker`
149
130
def test_goodTicker_withProxy (self ):
150
131
# that yfinance works when full api is called on same instance of ticker
151
132
152
133
tkr = "IBM"
153
134
dat = yf .Ticker (tkr , session = self .session )
154
135
155
- dat ._fetch_ticker_tz (proxy = self .proxy , timeout = 5 , debug_mode = False , raise_errors = False )
156
- dat ._get_ticker_tz (proxy = self .proxy , timeout = 5 , debug_mode = False , raise_errors = False )
136
+ dat ._fetch_ticker_tz (proxy = self .proxy , timeout = 5 )
137
+ dat ._get_ticker_tz (proxy = self .proxy , timeout = 5 )
157
138
dat .history (period = "1wk" , proxy = self .proxy )
158
139
159
- v = dat .stats (proxy = self .proxy )
160
- self .assertIsNotNone (v )
161
- self .assertTrue (len (v ) > 0 )
162
-
163
- v = dat .get_recommendations (proxy = self .proxy )
164
- self .assertIsNotNone (v )
165
- self .assertFalse (v .empty )
166
-
167
- v = dat .get_calendar (proxy = self .proxy )
168
- self .assertIsNotNone (v )
169
- self .assertFalse (v .empty )
170
-
171
140
v = dat .get_major_holders (proxy = self .proxy )
172
141
self .assertIsNotNone (v )
173
142
self .assertFalse (v .empty )
@@ -184,38 +153,6 @@ def test_goodTicker_withProxy(self):
184
153
self .assertIsNotNone (v )
185
154
self .assertTrue (len (v ) > 0 )
186
155
187
- v = dat .get_sustainability (proxy = self .proxy )
188
- self .assertIsNotNone (v )
189
- self .assertFalse (v .empty )
190
-
191
- v = dat .get_recommendations_summary (proxy = self .proxy )
192
- self .assertIsNotNone (v )
193
- self .assertFalse (v .empty )
194
-
195
- v = dat .get_analyst_price_target (proxy = self .proxy )
196
- self .assertIsNotNone (v )
197
- self .assertFalse (v .empty )
198
-
199
- v = dat .get_rev_forecast (proxy = self .proxy )
200
- self .assertIsNotNone (v )
201
- self .assertFalse (v .empty )
202
-
203
- v = dat .get_earnings_forecast (proxy = self .proxy )
204
- self .assertIsNotNone (v )
205
- self .assertFalse (v .empty )
206
-
207
- v = dat .get_trend_details (proxy = self .proxy )
208
- self .assertIsNotNone (v )
209
- self .assertFalse (v .empty )
210
-
211
- v = dat .get_earnings_trend (proxy = self .proxy )
212
- self .assertIsNotNone (v )
213
- self .assertFalse (v .empty )
214
-
215
- v = dat .get_earnings (proxy = self .proxy )
216
- self .assertIsNotNone (v )
217
- self .assertFalse (v .empty )
218
-
219
156
v = dat .get_income_stmt (proxy = self .proxy )
220
157
self .assertIsNotNone (v )
221
158
self .assertFalse (v .empty )
@@ -244,10 +181,6 @@ def test_goodTicker_withProxy(self):
244
181
self .assertIsNotNone (v )
245
182
self .assertFalse (v .empty )
246
183
247
- v = dat .get_shares (proxy = self .proxy )
248
- self .assertIsNotNone (v )
249
- self .assertFalse (v .empty )
250
-
251
184
v = dat .get_shares_full (proxy = self .proxy )
252
185
self .assertIsNotNone (v )
253
186
self .assertFalse (v .empty )
@@ -264,11 +197,60 @@ def test_goodTicker_withProxy(self):
264
197
self .assertIsNotNone (v )
265
198
self .assertFalse (v .empty )
266
199
267
- # TODO: enable after merge
268
- # dat.get_history_metadata(proxy=self.proxy)
200
+ dat .get_history_metadata (proxy = self .proxy )
201
+ self .assertIsNotNone (v )
202
+ self .assertTrue (len (v ) > 0 )
203
+
204
+ # Below will fail because not ported to Yahoo API
205
+
206
+ # v = dat.stats(proxy=self.proxy)
269
207
# self.assertIsNotNone(v)
270
208
# self.assertTrue(len(v) > 0)
271
209
210
+ # v = dat.get_recommendations(proxy=self.proxy)
211
+ # self.assertIsNotNone(v)
212
+ # self.assertFalse(v.empty)
213
+
214
+ # v = dat.get_calendar(proxy=self.proxy)
215
+ # self.assertIsNotNone(v)
216
+ # self.assertFalse(v.empty)
217
+
218
+ # v = dat.get_sustainability(proxy=self.proxy)
219
+ # self.assertIsNotNone(v)
220
+ # self.assertFalse(v.empty)
221
+
222
+ # v = dat.get_recommendations_summary(proxy=self.proxy)
223
+ # self.assertIsNotNone(v)
224
+ # self.assertFalse(v.empty)
225
+
226
+ # v = dat.get_analyst_price_target(proxy=self.proxy)
227
+ # self.assertIsNotNone(v)
228
+ # self.assertFalse(v.empty)
229
+
230
+ # v = dat.get_rev_forecast(proxy=self.proxy)
231
+ # self.assertIsNotNone(v)
232
+ # self.assertFalse(v.empty)
233
+
234
+ # v = dat.get_earnings_forecast(proxy=self.proxy)
235
+ # self.assertIsNotNone(v)
236
+ # self.assertFalse(v.empty)
237
+
238
+ # v = dat.get_trend_details(proxy=self.proxy)
239
+ # self.assertIsNotNone(v)
240
+ # self.assertFalse(v.empty)
241
+
242
+ # v = dat.get_earnings_trend(proxy=self.proxy)
243
+ # self.assertIsNotNone(v)
244
+ # self.assertFalse(v.empty)
245
+
246
+ # v = dat.get_earnings(proxy=self.proxy)
247
+ # self.assertIsNotNone(v)
248
+ # self.assertFalse(v.empty)
249
+
250
+ # v = dat.get_shares(proxy=self.proxy)
251
+ # self.assertIsNotNone(v)
252
+ # self.assertFalse(v.empty)
253
+
272
254
273
255
class TestTickerHistory (unittest .TestCase ):
274
256
session = None
@@ -312,16 +294,21 @@ def test_no_expensive_calls_introduced(self):
312
294
As doing other type of scraping calls than "query2.finance.yahoo.com/v8/finance/chart" to yahoo website
313
295
will quickly trigger spam-block when doing bulk download of history data.
314
296
"""
315
- session = requests_cache .CachedSession (backend = 'memory' )
316
- ticker = yf .Ticker ("GOOGL" , session = session )
317
- ticker .history ("1y" )
318
- actual_urls_called = tuple ([r .url for r in session .cache .filter ()])
319
- session .close ()
297
+ symbol = "GOOGL"
298
+ range = "1y"
299
+ with requests_cache .CachedSession (backend = "memory" ) as session :
300
+ ticker = yf .Ticker (symbol , session = session )
301
+ ticker .history (range )
302
+ actual_urls_called = tuple ([r .url for r in session .cache .filter ()])
303
+
320
304
expected_urls = (
321
- 'https://query2.finance.yahoo.com/v8/finance/chart/GOOGL?events=div,splits,capitalGains&includePrePost=False&interval=1d&range=1y' ,
305
+ f"https://query2.finance.yahoo.com/v8/finance/chart/{ symbol } ?events=div%2Csplits%2CcapitalGains&includePrePost=False&interval=1d&range={ range } " ,
306
+ )
307
+ self .assertEqual (
308
+ expected_urls ,
309
+ actual_urls_called ,
310
+ "Different than expected url used to fetch history."
322
311
)
323
- self .assertEqual (expected_urls , actual_urls_called , "Different than expected url used to fetch history." )
324
-
325
312
def test_dividends (self ):
326
313
data = self .ticker .dividends
327
314
self .assertIsInstance (data , pd .Series , "data has wrong type" )
0 commit comments