earningscall 0.0.16__tar.gz → 0.0.18__tar.gz
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.
- {earningscall-0.0.16 → earningscall-0.0.18}/.github/workflows/test.yml +1 -1
- {earningscall-0.0.16 → earningscall-0.0.18}/.gitignore +1 -0
- earningscall-0.0.18/.python-version +1 -0
- earningscall-0.0.18/CHANGELOG.md +22 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/DEVELOPMENT.md +6 -2
- {earningscall-0.0.16 → earningscall-0.0.18}/PKG-INFO +2 -2
- {earningscall-0.0.16 → earningscall-0.0.18}/TODO.md +0 -1
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/api.py +75 -5
- earningscall-0.0.18/earningscall/company.py +110 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/symbols.py +1 -1
- {earningscall-0.0.16 → earningscall-0.0.18}/hatch.toml +1 -1
- {earningscall-0.0.16 → earningscall-0.0.18}/pyproject.toml +9 -1
- earningscall-0.0.18/scripts/download_audio_files.py +28 -0
- earningscall-0.0.18/scripts/download_sp500_audio_files.py +30 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/demo-symbols-v2-alpha.yaml +1 -1
- earningscall-0.0.18/tests/data/msft-q1-2022-audio-file-short-clip.yaml +5510 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/msft-transcript-response.yaml +1 -1
- earningscall-0.0.18/tests/test_download_audio_files.py +53 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_get_sp500_companies_api.py +3 -3
- earningscall-0.0.16/.python-version +0 -1
- earningscall-0.0.16/CHANGELOG.md +0 -9
- earningscall-0.0.16/earningscall/company.py +0 -55
- {earningscall-0.0.16 → earningscall-0.0.18}/.github/workflows/release.yml +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/LICENSE +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/README.md +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/__init__.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/errors.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/event.py +1 -1
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/exports.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/sectors.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/transcript.py +1 -1
- {earningscall-0.0.16 → earningscall-0.0.18}/earningscall/utils.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/scripts/get_all_company_transcripts.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/scripts/get_single_transcript.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/scripts/list_companies.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/setup.cfg +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/demo-symbols-v2.yaml +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/msft-company-events.yaml +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/symbols-v2-missing-edge-cases.yaml +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/symbols-v2.yaml +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/symbols.txt +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/data/symbols.yaml +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_earnings_event.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_get_company_events.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_get_transcript.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_helper.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_responses_mocking.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_sectors.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_symbols.py +0 -0
- {earningscall-0.0.16 → earningscall-0.0.18}/tests/test_utils.py +0 -0
@@ -35,7 +35,7 @@ jobs:
|
|
35
35
|
python-version: ${{ matrix.python-version }}
|
36
36
|
|
37
37
|
- name: Install Hatch
|
38
|
-
run: pip install --upgrade hatch
|
38
|
+
run: pip install --upgrade hatch
|
39
39
|
|
40
40
|
- if: matrix.python-version == '3.9' && runner.os == 'Linux'
|
41
41
|
name: Lint
|
@@ -0,0 +1 @@
|
|
1
|
+
3.12.6
|
@@ -0,0 +1,22 @@
|
|
1
|
+
## Release `0.0.18` - 2024-10-01
|
2
|
+
|
3
|
+
* Add Download Audio File Feature: add `download_audio_file` function to the `Company` class.
|
4
|
+
* Add "LSE" to the list of exchanges.
|
5
|
+
|
6
|
+
## Release `0.0.17` - 2024-08-04
|
7
|
+
|
8
|
+
* Remove apikey from the ignored parameters list.
|
9
|
+
|
10
|
+
When `apikey=demo`, the API will return different results. If we change
|
11
|
+
apikey to some other value, then the old result from when `apikey=demo`
|
12
|
+
would be returned. This is incorrect.
|
13
|
+
|
14
|
+
## Release `0.0.16` - 2024-06-12
|
15
|
+
* Bump version number.
|
16
|
+
|
17
|
+
## Release `0.0.15` - 2024-06-12
|
18
|
+
* Remove `dataclasses` as a project dependency. It is built directly into Python.
|
19
|
+
|
20
|
+
## Release `0.0.14` - 2024-06-08
|
21
|
+
* Add caching to improve client performance.
|
22
|
+
* Add `company_info` attribute in `Company` class.
|
@@ -1,7 +1,12 @@
|
|
1
1
|
# Development
|
2
2
|
|
3
|
-
First, install Hatch. See the Hatch [installation instructions](https://hatch.pypa.io/latest/install/).
|
3
|
+
First, install Hatch plus other build dependencies. See the Hatch [installation instructions](https://hatch.pypa.io/latest/install/).
|
4
4
|
|
5
|
+
My preferred way to install it is to use `pip`:
|
6
|
+
|
7
|
+
```shell
|
8
|
+
pip install hatch coverage black
|
9
|
+
```
|
5
10
|
|
6
11
|
## Run Build
|
7
12
|
|
@@ -9,7 +14,6 @@ First, install Hatch. See the Hatch [installation instructions](https://hatch.p
|
|
9
14
|
hatch build
|
10
15
|
```
|
11
16
|
|
12
|
-
|
13
17
|
### Running Unit Tests
|
14
18
|
|
15
19
|
```shell
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: earningscall
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.18
|
4
4
|
Summary: The EarningsCall Python library provides convenient access to the EarningsCall API. It includes a pre-defined set of classes for API resources that initialize themselves dynamically from API responses.
|
5
5
|
Project-URL: Homepage, https://earningscall.biz
|
6
6
|
Project-URL: Documentation, https://github.com/EarningsCall/earningscall-python
|
@@ -31,7 +31,7 @@ License: MIT License
|
|
31
31
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
32
32
|
SOFTWARE.
|
33
33
|
License-File: LICENSE
|
34
|
-
Keywords: earnings call,earnings call transcripts,earnings calls,transcripts
|
34
|
+
Keywords: earning call app,earnings call,earnings call api,earnings call app,earnings call transcript api,earnings call transcripts,earnings call transcripts api,earnings calls,earnings transcript api,listen to earnings calls,transcripts,where to listen to earnings calls
|
35
35
|
Classifier: Development Status :: 3 - Alpha
|
36
36
|
Classifier: Intended Audience :: Developers
|
37
37
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -34,7 +34,7 @@ def cache_session() -> CachedSession:
|
|
34
34
|
backend="sqlite",
|
35
35
|
cache_control=True,
|
36
36
|
use_temp=True,
|
37
|
-
ignored_parameters=[
|
37
|
+
ignored_parameters=[],
|
38
38
|
)
|
39
39
|
|
40
40
|
|
@@ -46,7 +46,22 @@ def purge_cache():
|
|
46
46
|
return cache_session().cache.clear()
|
47
47
|
|
48
48
|
|
49
|
-
def do_get(
|
49
|
+
def do_get(
|
50
|
+
path: str,
|
51
|
+
use_cache: bool = False,
|
52
|
+
**kwargs,
|
53
|
+
) -> requests.Response:
|
54
|
+
"""
|
55
|
+
Do a GET request to the API.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
path (str): The path to request.
|
59
|
+
use_cache (bool): Whether to use the cache.
|
60
|
+
**kwargs: Additional arguments to pass to the request.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
requests.Response: The response from the API.
|
64
|
+
"""
|
50
65
|
params = {
|
51
66
|
**api_key_param(),
|
52
67
|
**kwargs.get("params", {}),
|
@@ -56,7 +71,11 @@ def do_get(path: str, use_cache: bool = False, **kwargs):
|
|
56
71
|
if use_cache and earningscall.enable_requests_cache:
|
57
72
|
return cache_session().get(url, params=params)
|
58
73
|
else:
|
59
|
-
return requests.get(
|
74
|
+
return requests.get(
|
75
|
+
url,
|
76
|
+
params=params,
|
77
|
+
stream=kwargs.get("stream"),
|
78
|
+
)
|
60
79
|
|
61
80
|
|
62
81
|
def get_events(exchange: str, symbol: str):
|
@@ -72,8 +91,25 @@ def get_events(exchange: str, symbol: str):
|
|
72
91
|
return response.json()
|
73
92
|
|
74
93
|
|
75
|
-
def get_transcript(
|
76
|
-
|
94
|
+
def get_transcript(
|
95
|
+
exchange: str,
|
96
|
+
symbol: str,
|
97
|
+
year: int,
|
98
|
+
quarter: int,
|
99
|
+
level: Optional[int] = None,
|
100
|
+
) -> Optional[str]:
|
101
|
+
"""
|
102
|
+
Get the transcript for a given exchange, symbol, year, and quarter.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
exchange (str): The exchange to get the transcript for.
|
106
|
+
symbol (str): The symbol to get the transcript for.
|
107
|
+
year (int): The year to get the transcript for.
|
108
|
+
quarter (int): The quarter to get the transcript for.
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Optional[str]: The transcript for the given exchange, symbol, year, and quarter.
|
112
|
+
"""
|
77
113
|
log.debug(f"get_transcript year: {year} quarter: {quarter}")
|
78
114
|
params = {
|
79
115
|
**api_key_param(),
|
@@ -81,6 +117,7 @@ def get_transcript(exchange: str, symbol: str, year: int, quarter: int) -> Optio
|
|
81
117
|
"symbol": symbol,
|
82
118
|
"year": str(year),
|
83
119
|
"quarter": str(quarter),
|
120
|
+
"level": str(level or 1),
|
84
121
|
}
|
85
122
|
response = do_get("transcript", params=params)
|
86
123
|
if response.status_code != 200:
|
@@ -102,3 +139,36 @@ def get_sp500_companies_txt_file():
|
|
102
139
|
if response.status_code != 200:
|
103
140
|
return None
|
104
141
|
return response.text
|
142
|
+
|
143
|
+
|
144
|
+
def download_audio_file(
|
145
|
+
exchange: str,
|
146
|
+
symbol: str,
|
147
|
+
year: int,
|
148
|
+
quarter: int,
|
149
|
+
file_name: Optional[str] = None,
|
150
|
+
) -> Optional[str]:
|
151
|
+
"""
|
152
|
+
Get the audio for a given exchange, symbol, year, and quarter.
|
153
|
+
|
154
|
+
Args:
|
155
|
+
exchange (str): The exchange to get the audio for.
|
156
|
+
symbol (str): The symbol to get the audio for.
|
157
|
+
year (int): The year to get the audio for.
|
158
|
+
quarter (int): The quarter to get the audio for.
|
159
|
+
filename (Optional[str]): The filename to save the audio to.
|
160
|
+
"""
|
161
|
+
params = {
|
162
|
+
**api_key_param(),
|
163
|
+
"exchange": exchange,
|
164
|
+
"symbol": symbol,
|
165
|
+
"year": str(year),
|
166
|
+
"quarter": str(quarter),
|
167
|
+
}
|
168
|
+
local_filename = file_name or f"{exchange}_{symbol}_{year}_{quarter}.mp3"
|
169
|
+
with do_get("audio", params=params, stream=True) as response:
|
170
|
+
response.raise_for_status()
|
171
|
+
with open(local_filename, "wb") as f:
|
172
|
+
for chunk in response.iter_content(chunk_size=8192):
|
173
|
+
f.write(chunk)
|
174
|
+
return local_filename
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import Optional, List
|
3
|
+
|
4
|
+
from earningscall import api
|
5
|
+
from earningscall.event import EarningsEvent
|
6
|
+
from earningscall.symbols import CompanyInfo
|
7
|
+
from earningscall.transcript import Transcript
|
8
|
+
|
9
|
+
log = logging.getLogger(__file__)
|
10
|
+
|
11
|
+
|
12
|
+
class Company:
|
13
|
+
|
14
|
+
company_info: CompanyInfo
|
15
|
+
name: Optional[str]
|
16
|
+
_events: Optional[List[EarningsEvent]]
|
17
|
+
|
18
|
+
def __init__(self, company_info: CompanyInfo):
|
19
|
+
if not company_info:
|
20
|
+
raise ValueError("company_info must be present.")
|
21
|
+
self.company_info = company_info
|
22
|
+
self.name = company_info.name
|
23
|
+
self._events = None
|
24
|
+
|
25
|
+
def __str__(self):
|
26
|
+
return str(self.name)
|
27
|
+
|
28
|
+
def _get_events(self) -> List[EarningsEvent]:
|
29
|
+
if not self.company_info.exchange or not self.company_info.symbol:
|
30
|
+
return []
|
31
|
+
raw_response = api.get_events(self.company_info.exchange, self.company_info.symbol)
|
32
|
+
if not raw_response:
|
33
|
+
return []
|
34
|
+
return [EarningsEvent.from_dict(event) for event in raw_response["events"]] # type: ignore
|
35
|
+
|
36
|
+
def events(self) -> List[EarningsEvent]:
|
37
|
+
if not self._events:
|
38
|
+
self._events = self._get_events()
|
39
|
+
return self._events
|
40
|
+
|
41
|
+
def get_transcript(
|
42
|
+
self,
|
43
|
+
year: Optional[int] = None,
|
44
|
+
quarter: Optional[int] = None,
|
45
|
+
event: Optional[EarningsEvent] = None,
|
46
|
+
) -> Optional[Transcript]:
|
47
|
+
"""
|
48
|
+
Get the transcript for a given year and quarter.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
year (Optional[int]): The year to get the transcript for.
|
52
|
+
quarter (Optional[int]): The quarter to get the transcript for.
|
53
|
+
event (Optional[EarningsEvent]): The event to get the transcript for.
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
Optional[Transcript]: The transcript for the given year and quarter.
|
57
|
+
"""
|
58
|
+
if not self.company_info.exchange or not self.company_info.symbol:
|
59
|
+
return None
|
60
|
+
if (not year or not quarter) and event:
|
61
|
+
year = event.year
|
62
|
+
quarter = event.quarter
|
63
|
+
if (not year or not quarter) and not event:
|
64
|
+
raise ValueError("Must specify either event or year and quarter")
|
65
|
+
resp = api.get_transcript(
|
66
|
+
self.company_info.exchange,
|
67
|
+
self.company_info.symbol,
|
68
|
+
year, # type: ignore
|
69
|
+
quarter, # type: ignore
|
70
|
+
level=None, # TODO: Pass level to API
|
71
|
+
)
|
72
|
+
# TODO: Parse Advanced transcript data
|
73
|
+
if not resp:
|
74
|
+
return None
|
75
|
+
return Transcript.from_dict(resp) # type: ignore
|
76
|
+
|
77
|
+
def download_audio_file(
|
78
|
+
self,
|
79
|
+
year: Optional[int] = None,
|
80
|
+
quarter: Optional[int] = None,
|
81
|
+
event: Optional[EarningsEvent] = None,
|
82
|
+
file_name: Optional[str] = None,
|
83
|
+
) -> Optional[str]:
|
84
|
+
"""
|
85
|
+
Download the audio file for a given year and quarter.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
year (Optional[int]): The year to get the audio for.
|
89
|
+
quarter (Optional[int]): The quarter to get the audio for.
|
90
|
+
event (Optional[EarningsEvent]): The event to get the audio for.
|
91
|
+
file_name (Optional[str]): The file name to save the audio to.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Optional[str]: The audio for the given year and quarter.
|
95
|
+
"""
|
96
|
+
if not self.company_info.exchange or not self.company_info.symbol:
|
97
|
+
return None
|
98
|
+
if (not year or not quarter) and event:
|
99
|
+
year = event.year
|
100
|
+
quarter = event.quarter
|
101
|
+
if (not year or not quarter) and not event:
|
102
|
+
raise ValueError("Must specify either event or year and quarter")
|
103
|
+
resp = api.download_audio_file(
|
104
|
+
exchange=self.company_info.exchange,
|
105
|
+
symbol=self.company_info.symbol,
|
106
|
+
year=year, # type: ignore
|
107
|
+
quarter=quarter, # type: ignore
|
108
|
+
file_name=file_name,
|
109
|
+
)
|
110
|
+
return resp
|
@@ -8,7 +8,7 @@ from earningscall.errors import InsufficientApiAccessError
|
|
8
8
|
from earningscall.sectors import sector_to_index, industry_to_index, index_to_sector, index_to_industry
|
9
9
|
|
10
10
|
# WARNING: Add new indexes to the *END* of this list
|
11
|
-
EXCHANGES_IN_ORDER = ["NYSE", "NASDAQ", "AMEX", "TSX", "TSXV", "OTC"]
|
11
|
+
EXCHANGES_IN_ORDER = ["NYSE", "NASDAQ", "AMEX", "TSX", "TSXV", "OTC", "LSE"]
|
12
12
|
|
13
13
|
log = logging.getLogger(__file__)
|
14
14
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "earningscall"
|
3
|
-
version = "0.0.
|
3
|
+
version = "0.0.18"
|
4
4
|
description = "The EarningsCall Python library provides convenient access to the EarningsCall API. It includes a pre-defined set of classes for API resources that initialize themselves dynamically from API responses."
|
5
5
|
readme = "README.md"
|
6
6
|
authors = [
|
@@ -16,7 +16,15 @@ license = { file = "LICENSE" }
|
|
16
16
|
keywords = [
|
17
17
|
"earnings calls",
|
18
18
|
"earnings call",
|
19
|
+
"earnings call api",
|
19
20
|
"earnings call transcripts",
|
21
|
+
"earnings call transcripts api",
|
22
|
+
"earnings call transcript api",
|
23
|
+
"earnings call app",
|
24
|
+
"earning call app",
|
25
|
+
"listen to earnings calls",
|
26
|
+
"where to listen to earnings calls",
|
27
|
+
"earnings transcript api",
|
20
28
|
"transcripts",
|
21
29
|
]
|
22
30
|
classifiers = [
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import os
|
2
|
+
from earningscall import get_company
|
3
|
+
|
4
|
+
|
5
|
+
directory = "audio_files"
|
6
|
+
os.makedirs(directory, exist_ok=True)
|
7
|
+
|
8
|
+
|
9
|
+
def download_audio_files(company):
|
10
|
+
print(f"Downloading all audio files for: {company}..")
|
11
|
+
for event in company.events():
|
12
|
+
file_name = os.path.join(
|
13
|
+
directory,
|
14
|
+
f"{company.company_info.exchange}_{company.company_info.symbol}_{event.year}_Q{event.quarter}.mp3",
|
15
|
+
)
|
16
|
+
if os.path.exists(file_name):
|
17
|
+
print(f"* {company.company_info.symbol} Q{event.quarter} {event.year} -- already downloaded")
|
18
|
+
else:
|
19
|
+
print(f"* Downloading audio file for {company.company_info.symbol} Q{event.quarter} {event.year}...")
|
20
|
+
audio_file = company.download_audio_file(event=event, file_name=file_name)
|
21
|
+
if audio_file:
|
22
|
+
print(f" Downloaded audio file: \"{audio_file}\"")
|
23
|
+
else:
|
24
|
+
print(f" No audio file found for {company.company_info.symbol} Q{event.quarter} {event.year}")
|
25
|
+
|
26
|
+
|
27
|
+
company = get_company("aapl") # Lookup Apple, Inc by its ticker symbol, "AAPL"
|
28
|
+
download_audio_files(company)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import os
|
2
|
+
from earningscall import get_sp500_companies
|
3
|
+
|
4
|
+
# TODO: Set your API key here:
|
5
|
+
# earningscall.api_key = "YOUR SECRET API KEY GOES HERE"
|
6
|
+
|
7
|
+
directory = "audio_files"
|
8
|
+
os.makedirs(directory, exist_ok=True)
|
9
|
+
|
10
|
+
|
11
|
+
def download_audio_files(company):
|
12
|
+
print(f"Downloading all audio files for: {company}..")
|
13
|
+
for event in company.events():
|
14
|
+
file_name = os.path.join(
|
15
|
+
directory,
|
16
|
+
f"{company.company_info.exchange}_{company.company_info.symbol}_{event.year}_Q{event.quarter}.mp3",
|
17
|
+
)
|
18
|
+
if os.path.exists(file_name):
|
19
|
+
print(f"* {company.company_info.symbol} Q{event.quarter} {event.year} -- already downloaded")
|
20
|
+
else:
|
21
|
+
print(f"* Downloading audio file for {company.company_info.symbol} Q{event.quarter} {event.year}...")
|
22
|
+
audio_file = company.download_audio_file(event=event, file_name=file_name)
|
23
|
+
if audio_file:
|
24
|
+
print(f" Downloaded audio file: \"{audio_file}\"")
|
25
|
+
else:
|
26
|
+
print(f" No audio file found for {company.company_info.symbol} Q{event.quarter} {event.year}")
|
27
|
+
|
28
|
+
|
29
|
+
for company in get_sp500_companies():
|
30
|
+
download_audio_files(company)
|
@@ -716,4 +716,4 @@ responses:
|
|
716
716
|
content_type: text/plain
|
717
717
|
method: GET
|
718
718
|
status: 200
|
719
|
-
url: https://v2.api.earningscall.biz/transcript?apikey=demo&exchange=NASDAQ&symbol=AAPL&year=2022&quarter=1
|
719
|
+
url: https://v2.api.earningscall.biz/transcript?apikey=demo&exchange=NASDAQ&symbol=AAPL&year=2022&quarter=1&level=1
|