finviz-data 1.2.2__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.
- finviz_data-1.2.2/PKG-INFO +45 -0
- finviz_data-1.2.2/README.md +34 -0
- finviz_data-1.2.2/finviz_data/__init__.py +0 -0
- finviz_data-1.2.2/finviz_data/finviz_data.py +209 -0
- finviz_data-1.2.2/finviz_data.egg-info/PKG-INFO +45 -0
- finviz_data-1.2.2/finviz_data.egg-info/SOURCES.txt +10 -0
- finviz_data-1.2.2/finviz_data.egg-info/dependency_links.txt +1 -0
- finviz_data-1.2.2/finviz_data.egg-info/requires.txt +2 -0
- finviz_data-1.2.2/finviz_data.egg-info/top_level.txt +1 -0
- finviz_data-1.2.2/pyproject.toml +23 -0
- finviz_data-1.2.2/setup.cfg +4 -0
- finviz_data-1.2.2/tests/test_finviz_data.py +149 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: finviz-data
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: Simple package to get data from finviz.com
|
|
5
|
+
Author-email: Dennis Iversen <dennis.iversen@gmail.com>
|
|
6
|
+
Project-URL: Repository, https://github.com/diversen/finviz-data
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: beautifulsoup4<5,>=4.12
|
|
10
|
+
Requires-Dist: curl-cffi<1,>=0.7
|
|
11
|
+
|
|
12
|
+
# finviz-data
|
|
13
|
+
|
|
14
|
+
A simple package for getting fundamental data from finviz.com for a single ticker.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv add git+https://github.com/diversen/finviz-data
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Development
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv sync
|
|
26
|
+
uv run python -m unittest discover
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
|
|
33
|
+
from finviz_data import finviz_data
|
|
34
|
+
|
|
35
|
+
# Get the html soup for a single ticker
|
|
36
|
+
soup = finviz_data.get_soup('AAPL')
|
|
37
|
+
|
|
38
|
+
# Get the fundamentals for a single ticker
|
|
39
|
+
fundamentals = finviz_data.get_fundamentals(soup)
|
|
40
|
+
|
|
41
|
+
# Get the fundamentals where all is formatted to float values where possible
|
|
42
|
+
fundamentals = finviz_data.get_fundementals_float(soup)
|
|
43
|
+
|
|
44
|
+
# Get basic company info, sector, ticker etc.
|
|
45
|
+
company_info = finviz_data.get_company_info(soup)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# finviz-data
|
|
2
|
+
|
|
3
|
+
A simple package for getting fundamental data from finviz.com for a single ticker.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add git+https://github.com/diversen/finviz-data
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv sync
|
|
15
|
+
uv run python -m unittest discover
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
|
|
22
|
+
from finviz_data import finviz_data
|
|
23
|
+
|
|
24
|
+
# Get the html soup for a single ticker
|
|
25
|
+
soup = finviz_data.get_soup('AAPL')
|
|
26
|
+
|
|
27
|
+
# Get the fundamentals for a single ticker
|
|
28
|
+
fundamentals = finviz_data.get_fundamentals(soup)
|
|
29
|
+
|
|
30
|
+
# Get the fundamentals where all is formatted to float values where possible
|
|
31
|
+
fundamentals = finviz_data.get_fundementals_float(soup)
|
|
32
|
+
|
|
33
|
+
# Get basic company info, sector, ticker etc.
|
|
34
|
+
company_info = finviz_data.get_company_info(soup)
|
|
File without changes
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from bs4 import BeautifulSoup
|
|
4
|
+
from curl_cffi import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_soup(ticker) -> BeautifulSoup:
|
|
8
|
+
url = f"https://finviz.com/stock?t={ticker}&p=d"
|
|
9
|
+
html = requests.get(url, impersonate="chrome").text
|
|
10
|
+
soup = BeautifulSoup(html, "html.parser")
|
|
11
|
+
return soup
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_fundamentals(soup: BeautifulSoup) -> dict:
|
|
15
|
+
# get table with class='snapshot-table2'
|
|
16
|
+
table = soup.find("table", {"class": "snapshot-table2"})
|
|
17
|
+
|
|
18
|
+
# Initialize a dictionary to store the key-value pairs
|
|
19
|
+
financial_data = {}
|
|
20
|
+
|
|
21
|
+
# Iterate through all rows of the table
|
|
22
|
+
for row in table.find_all("tr"):
|
|
23
|
+
# Each cell in the row
|
|
24
|
+
cells = row.find_all("td")
|
|
25
|
+
for i in range(0, len(cells), 2): # Step by 2 as key and value are in pairs
|
|
26
|
+
key = cells[i].get_text().strip()
|
|
27
|
+
value = cells[i + 1].get_text().strip()
|
|
28
|
+
financial_data[key] = value
|
|
29
|
+
|
|
30
|
+
return financial_data
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_fundamentals_float(soup: BeautifulSoup) -> dict:
|
|
34
|
+
fundamentals = get_fundamentals(soup)
|
|
35
|
+
fundamentals_float = _convert_to_floats(fundamentals)
|
|
36
|
+
|
|
37
|
+
return fundamentals_float
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_company_info(soup: BeautifulSoup) -> dict:
|
|
41
|
+
base_info = {}
|
|
42
|
+
|
|
43
|
+
# Get ticker. Text ontent of quote-header_ticker-wrapper_ticker
|
|
44
|
+
ticker = soup.find("h1", class_="quote-header_ticker-wrapper_ticker").text
|
|
45
|
+
base_info["Ticker"] = ticker
|
|
46
|
+
|
|
47
|
+
# Get company name. Text content of h2.quote-header_ticker-wrapper_company a
|
|
48
|
+
company_name = (
|
|
49
|
+
soup.find("h2", class_="quote-header_ticker-wrapper_company").text
|
|
50
|
+
)
|
|
51
|
+
base_info["Company"] = company_name.strip()
|
|
52
|
+
|
|
53
|
+
# Finding the first inner div of the element with class 'quote-links'
|
|
54
|
+
first_inner_div = soup.find("div", class_="quote-links").find("div")
|
|
55
|
+
|
|
56
|
+
# Extracting all links from the first inner div
|
|
57
|
+
links = first_inner_div.find_all("a")
|
|
58
|
+
|
|
59
|
+
for link in links:
|
|
60
|
+
href = link.get("href", "")
|
|
61
|
+
if "f=sec_" in href:
|
|
62
|
+
base_info["Sector"] = link.text
|
|
63
|
+
elif "f=ind_" in href:
|
|
64
|
+
base_info["Industry"] = link.text
|
|
65
|
+
elif "f=geo_" in href:
|
|
66
|
+
base_info["Country"] = link.text
|
|
67
|
+
elif "f=exch_" in href:
|
|
68
|
+
base_info["Exchange"] = link.text
|
|
69
|
+
|
|
70
|
+
return base_info
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _is_float_like(val: str) -> bool:
|
|
74
|
+
try:
|
|
75
|
+
float(val)
|
|
76
|
+
return True
|
|
77
|
+
except ValueError:
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _convert_value(value: str):
|
|
82
|
+
if value is None:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
value = value.strip()
|
|
86
|
+
|
|
87
|
+
if value in ("", "-"):
|
|
88
|
+
return None
|
|
89
|
+
elif value.endswith("%") and _is_float_like(value.strip("%")):
|
|
90
|
+
return float(value.strip("%")) / 100
|
|
91
|
+
elif value.endswith("M") and _is_float_like(value.strip("M")):
|
|
92
|
+
return float(value.strip("M")) * 1e6
|
|
93
|
+
elif value.endswith("B") and _is_float_like(value.strip("B")):
|
|
94
|
+
return float(value.strip("B")) * 1e9
|
|
95
|
+
elif value.endswith("T") and _is_float_like(value.strip("T")):
|
|
96
|
+
return float(value.strip("T")) * 1e12
|
|
97
|
+
elif _is_float_like(value.replace(",", "")):
|
|
98
|
+
return float(value.replace(",", ""))
|
|
99
|
+
else:
|
|
100
|
+
return value
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _convert_date(value: str):
|
|
104
|
+
try:
|
|
105
|
+
return datetime.strptime(value, "%b %d, %Y").date().isoformat()
|
|
106
|
+
except ValueError:
|
|
107
|
+
return _convert_value(value)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _split_two_values(value: str, first_key: str, second_key: str) -> dict:
|
|
111
|
+
values = value.split()
|
|
112
|
+
if len(values) != 2:
|
|
113
|
+
return {first_key: _convert_value(value), second_key: None}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
first_key: _convert_value(values[0]),
|
|
117
|
+
second_key: _convert_value(values[1]),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _split_dividend(value: str, amount_key: str, yield_key: str) -> dict:
|
|
122
|
+
values = value.replace("(", "").replace(")", "").split()
|
|
123
|
+
if len(values) != 2:
|
|
124
|
+
return {amount_key: _convert_value(value), yield_key: None}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
amount_key: _convert_value(values[0]),
|
|
128
|
+
yield_key: _convert_value(values[1]),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _split_option_short(value: str) -> dict:
|
|
133
|
+
values = [item.strip() for item in value.split("/")]
|
|
134
|
+
if len(values) != 2:
|
|
135
|
+
return {"Optionable": _convert_value(value), "Shortable": None}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"Optionable": values[0] == "Yes",
|
|
139
|
+
"Shortable": values[1] == "Yes",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _split_earnings(value: str) -> dict:
|
|
144
|
+
values = value.split()
|
|
145
|
+
if len(values) < 3:
|
|
146
|
+
return {"Earnings Date": _convert_value(value), "Earnings Time": None}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
"Earnings Date": " ".join(values[:-1]),
|
|
150
|
+
"Earnings Time": values[-1],
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _convert_to_floats(data_dict: dict) -> dict:
|
|
155
|
+
"""
|
|
156
|
+
Converts values in a dictionary to floats where applicable.
|
|
157
|
+
- Replaces single dash ('-') with None.
|
|
158
|
+
- Splits values representing a range (e.g., '10.86 - 19.08') into two separate keys.
|
|
159
|
+
- Converts values with '%' to proportionate floats.
|
|
160
|
+
- Converts values ending in 'M', 'B', or 'T' to their numeric equivalents in millions, billions, or trillions.
|
|
161
|
+
- Converts numeric strings with commas (e.g., '30,196,932') into floats.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
compound_fields = {
|
|
165
|
+
"Volatility": lambda value: _split_two_values(
|
|
166
|
+
value, "Volatility Week", "Volatility Month"
|
|
167
|
+
),
|
|
168
|
+
"52W High": lambda value: _split_two_values(
|
|
169
|
+
value, "52W High Price", "52W High Change"
|
|
170
|
+
),
|
|
171
|
+
"52W Low": lambda value: _split_two_values(
|
|
172
|
+
value, "52W Low Price", "52W Low Change"
|
|
173
|
+
),
|
|
174
|
+
"EPS past 3/5Y": lambda value: _split_two_values(
|
|
175
|
+
value, "EPS past 3Y", "EPS past 5Y"
|
|
176
|
+
),
|
|
177
|
+
"Sales past 3/5Y": lambda value: _split_two_values(
|
|
178
|
+
value, "Sales past 3Y", "Sales past 5Y"
|
|
179
|
+
),
|
|
180
|
+
"Dividend Gr. 3/5Y": lambda value: _split_two_values(
|
|
181
|
+
value, "Dividend Gr. 3Y", "Dividend Gr. 5Y"
|
|
182
|
+
),
|
|
183
|
+
"EPS/Sales Surpr.": lambda value: _split_two_values(
|
|
184
|
+
value, "EPS Surprise", "Sales Surprise"
|
|
185
|
+
),
|
|
186
|
+
"Dividend Est.": lambda value: _split_dividend(
|
|
187
|
+
value, "Dividend Est. Amount", "Dividend Est. Yield"
|
|
188
|
+
),
|
|
189
|
+
"Dividend TTM": lambda value: _split_dividend(
|
|
190
|
+
value, "Dividend TTM Amount", "Dividend TTM Yield"
|
|
191
|
+
),
|
|
192
|
+
"Option/Short": _split_option_short,
|
|
193
|
+
"Earnings": _split_earnings,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
date_fields = {"IPO", "Dividend Ex-Date"}
|
|
197
|
+
|
|
198
|
+
new_data = {}
|
|
199
|
+
for key, value in data_dict.items():
|
|
200
|
+
value = value.strip() if isinstance(value, str) else value
|
|
201
|
+
|
|
202
|
+
if key in compound_fields:
|
|
203
|
+
new_data.update(compound_fields[key](value))
|
|
204
|
+
elif key in date_fields:
|
|
205
|
+
new_data[key] = _convert_date(value)
|
|
206
|
+
else:
|
|
207
|
+
new_data[key] = _convert_value(value)
|
|
208
|
+
|
|
209
|
+
return new_data
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: finviz-data
|
|
3
|
+
Version: 1.2.2
|
|
4
|
+
Summary: Simple package to get data from finviz.com
|
|
5
|
+
Author-email: Dennis Iversen <dennis.iversen@gmail.com>
|
|
6
|
+
Project-URL: Repository, https://github.com/diversen/finviz-data
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: beautifulsoup4<5,>=4.12
|
|
10
|
+
Requires-Dist: curl-cffi<1,>=0.7
|
|
11
|
+
|
|
12
|
+
# finviz-data
|
|
13
|
+
|
|
14
|
+
A simple package for getting fundamental data from finviz.com for a single ticker.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv add git+https://github.com/diversen/finviz-data
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Development
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv sync
|
|
26
|
+
uv run python -m unittest discover
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
|
|
33
|
+
from finviz_data import finviz_data
|
|
34
|
+
|
|
35
|
+
# Get the html soup for a single ticker
|
|
36
|
+
soup = finviz_data.get_soup('AAPL')
|
|
37
|
+
|
|
38
|
+
# Get the fundamentals for a single ticker
|
|
39
|
+
fundamentals = finviz_data.get_fundamentals(soup)
|
|
40
|
+
|
|
41
|
+
# Get the fundamentals where all is formatted to float values where possible
|
|
42
|
+
fundamentals = finviz_data.get_fundementals_float(soup)
|
|
43
|
+
|
|
44
|
+
# Get basic company info, sector, ticker etc.
|
|
45
|
+
company_info = finviz_data.get_company_info(soup)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
finviz_data/__init__.py
|
|
4
|
+
finviz_data/finviz_data.py
|
|
5
|
+
finviz_data.egg-info/PKG-INFO
|
|
6
|
+
finviz_data.egg-info/SOURCES.txt
|
|
7
|
+
finviz_data.egg-info/dependency_links.txt
|
|
8
|
+
finviz_data.egg-info/requires.txt
|
|
9
|
+
finviz_data.egg-info/top_level.txt
|
|
10
|
+
tests/test_finviz_data.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
finviz_data
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "finviz-data"
|
|
3
|
+
version = "1.2.2"
|
|
4
|
+
description = "Simple package to get data from finviz.com"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Dennis Iversen", email = "dennis.iversen@gmail.com" },
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"beautifulsoup4>=4.12,<5",
|
|
12
|
+
"curl-cffi>=0.7,<1",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Repository = "https://github.com/diversen/finviz-data"
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["setuptools>=68"]
|
|
20
|
+
build-backend = "setuptools.build_meta"
|
|
21
|
+
|
|
22
|
+
[tool.setuptools.packages.find]
|
|
23
|
+
include = ["finviz_data*"]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from finviz_data import finviz_data
|
|
2
|
+
|
|
3
|
+
# import python unit test
|
|
4
|
+
import os
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from unittest.mock import Mock, patch
|
|
8
|
+
|
|
9
|
+
from bs4 import BeautifulSoup
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ticker = "AAPL"
|
|
13
|
+
FIXTURE = Path(__file__).resolve().parents[1] / "finviz_example.html"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestFinvizData(unittest.TestCase):
|
|
17
|
+
@classmethod
|
|
18
|
+
def setUpClass(cls):
|
|
19
|
+
cls.html = FIXTURE.read_text(encoding="utf-8")
|
|
20
|
+
cls.soup = BeautifulSoup(cls.html, "html.parser")
|
|
21
|
+
|
|
22
|
+
def test_get_soup(self):
|
|
23
|
+
response = Mock(text=self.html)
|
|
24
|
+
with patch("finviz_data.finviz_data.requests.get", return_value=response) as get:
|
|
25
|
+
soup = finviz_data.get_soup(ticker)
|
|
26
|
+
|
|
27
|
+
get.assert_called_once_with(
|
|
28
|
+
"https://finviz.com/stock?t=AAPL&p=d",
|
|
29
|
+
impersonate="chrome",
|
|
30
|
+
)
|
|
31
|
+
self.assertIsNotNone(soup)
|
|
32
|
+
self.assertEqual(soup.find("h1").get_text(strip=True), ticker)
|
|
33
|
+
|
|
34
|
+
@unittest.skipUnless(
|
|
35
|
+
os.getenv("FINVIZ_LIVE_TESTS") == "1",
|
|
36
|
+
"set FINVIZ_LIVE_TESTS=1 to run live Finviz tests",
|
|
37
|
+
)
|
|
38
|
+
def test_get_soup_live(self):
|
|
39
|
+
soup = finviz_data.get_soup(ticker)
|
|
40
|
+
page_text = soup.get_text(" ", strip=True)
|
|
41
|
+
|
|
42
|
+
if "temporarily rate limited" in page_text:
|
|
43
|
+
self.skipTest("Finviz temporarily rate limited this IP")
|
|
44
|
+
|
|
45
|
+
self.assertIsNotNone(soup)
|
|
46
|
+
table = soup.find("table", class_="snapshot-table2")
|
|
47
|
+
self.assertIsNotNone(table, page_text[:500])
|
|
48
|
+
|
|
49
|
+
header = soup.find("h1", class_="quote-header_ticker-wrapper_ticker")
|
|
50
|
+
self.assertIsNotNone(header, page_text[:500])
|
|
51
|
+
self.assertEqual(header.get_text(strip=True), ticker)
|
|
52
|
+
|
|
53
|
+
fundamentals = finviz_data.get_fundamentals(soup)
|
|
54
|
+
self.assertIn("Market Cap", fundamentals)
|
|
55
|
+
self.assertIn("P/E", fundamentals)
|
|
56
|
+
|
|
57
|
+
company_info = finviz_data.get_company_info(soup)
|
|
58
|
+
self.assertEqual(company_info["Ticker"], ticker)
|
|
59
|
+
self.assertIn("Company", company_info)
|
|
60
|
+
|
|
61
|
+
def test_get_fundamentals(self):
|
|
62
|
+
fundamentals = finviz_data.get_fundamentals(self.soup)
|
|
63
|
+
self.assertIsInstance(fundamentals, dict)
|
|
64
|
+
self.assertEqual(fundamentals["Market Cap"], "4282.54B")
|
|
65
|
+
self.assertEqual(fundamentals["P/E"], "35.27")
|
|
66
|
+
self.assertEqual(fundamentals["Volatility"], "3.27% 2.16%")
|
|
67
|
+
|
|
68
|
+
def test_get_company_info(self):
|
|
69
|
+
company_info = finviz_data.get_company_info(self.soup)
|
|
70
|
+
self.assertIsInstance(company_info, dict)
|
|
71
|
+
|
|
72
|
+
# check if company_info has all the keys
|
|
73
|
+
self.assertEqual(company_info["Ticker"], ticker)
|
|
74
|
+
self.assertEqual(company_info["Company"], "Apple Inc")
|
|
75
|
+
self.assertEqual(company_info["Sector"], "Technology")
|
|
76
|
+
self.assertEqual(company_info["Industry"], "Consumer Electronics")
|
|
77
|
+
self.assertEqual(company_info["Country"], "USA")
|
|
78
|
+
self.assertEqual(company_info["Exchange"], "NASD")
|
|
79
|
+
self.assertIn("Company", company_info)
|
|
80
|
+
self.assertIn("Sector", company_info)
|
|
81
|
+
self.assertIn("Industry", company_info)
|
|
82
|
+
self.assertIn("Country", company_info)
|
|
83
|
+
self.assertIn("Exchange", company_info)
|
|
84
|
+
|
|
85
|
+
def test_get_get_fundamentals_converted(self):
|
|
86
|
+
fundamentals_converted = finviz_data.get_fundamentals_float(self.soup)
|
|
87
|
+
self.assertIsInstance(fundamentals_converted, dict)
|
|
88
|
+
self.assertEqual(fundamentals_converted["Market Cap"], 4282.54e9)
|
|
89
|
+
self.assertEqual(fundamentals_converted["P/E"], 35.27)
|
|
90
|
+
self.assertAlmostEqual(fundamentals_converted["Volatility Week"], 0.0327)
|
|
91
|
+
self.assertAlmostEqual(fundamentals_converted["Volatility Month"], 0.0216)
|
|
92
|
+
self.assertEqual(fundamentals_converted["52W High Price"], 317.4)
|
|
93
|
+
self.assertAlmostEqual(fundamentals_converted["52W High Change"], -0.0813)
|
|
94
|
+
self.assertEqual(fundamentals_converted["52W Low Price"], 195.07)
|
|
95
|
+
self.assertAlmostEqual(fundamentals_converted["52W Low Change"], 0.4947)
|
|
96
|
+
self.assertAlmostEqual(fundamentals_converted["EPS past 3Y"], 0.0689)
|
|
97
|
+
self.assertAlmostEqual(fundamentals_converted["EPS past 5Y"], 0.1791)
|
|
98
|
+
self.assertAlmostEqual(fundamentals_converted["Sales past 3Y"], 0.0181)
|
|
99
|
+
self.assertAlmostEqual(fundamentals_converted["Sales past 5Y"], 0.0871)
|
|
100
|
+
self.assertAlmostEqual(fundamentals_converted["Dividend Gr. 3Y"], 0.0426)
|
|
101
|
+
self.assertAlmostEqual(fundamentals_converted["Dividend Gr. 5Y"], 0.0498)
|
|
102
|
+
self.assertEqual(fundamentals_converted["Dividend Est. Amount"], 1.08)
|
|
103
|
+
self.assertAlmostEqual(fundamentals_converted["Dividend Est. Yield"], 0.0037)
|
|
104
|
+
self.assertEqual(fundamentals_converted["Dividend TTM Amount"], 1.05)
|
|
105
|
+
self.assertAlmostEqual(fundamentals_converted["Dividend TTM Yield"], 0.0036)
|
|
106
|
+
self.assertEqual(fundamentals_converted["Optionable"], True)
|
|
107
|
+
self.assertEqual(fundamentals_converted["Shortable"], True)
|
|
108
|
+
self.assertAlmostEqual(fundamentals_converted["EPS Surprise"], 0.033)
|
|
109
|
+
self.assertAlmostEqual(fundamentals_converted["Sales Surprise"], 0.0158)
|
|
110
|
+
self.assertEqual(fundamentals_converted["Earnings Date"], "Apr 30")
|
|
111
|
+
self.assertEqual(fundamentals_converted["Earnings Time"], "AMC")
|
|
112
|
+
self.assertEqual(fundamentals_converted["IPO"], "1980-12-12")
|
|
113
|
+
self.assertEqual(fundamentals_converted["Dividend Ex-Date"], "2026-05-11")
|
|
114
|
+
self.assertIsNone(fundamentals_converted["Trades"])
|
|
115
|
+
|
|
116
|
+
self.assertNotIn("Volatility", fundamentals_converted)
|
|
117
|
+
self.assertNotIn("52W High", fundamentals_converted)
|
|
118
|
+
self.assertNotIn("52W Low", fundamentals_converted)
|
|
119
|
+
self.assertNotIn("EPS past 3/5Y", fundamentals_converted)
|
|
120
|
+
self.assertNotIn("Sales past 3/5Y", fundamentals_converted)
|
|
121
|
+
self.assertNotIn("Dividend Gr. 3/5Y", fundamentals_converted)
|
|
122
|
+
self.assertNotIn("Dividend Est.", fundamentals_converted)
|
|
123
|
+
self.assertNotIn("Dividend TTM", fundamentals_converted)
|
|
124
|
+
self.assertNotIn("Option/Short", fundamentals_converted)
|
|
125
|
+
self.assertNotIn("EPS/Sales Surpr.", fundamentals_converted)
|
|
126
|
+
self.assertNotIn("Earnings", fundamentals_converted)
|
|
127
|
+
|
|
128
|
+
def test_convert_percentage(self):
|
|
129
|
+
value = "1.39%"
|
|
130
|
+
converted_value = finviz_data._convert_value(value)
|
|
131
|
+
self.assertEqual(converted_value, 0.0139)
|
|
132
|
+
|
|
133
|
+
def test_convert_million(self):
|
|
134
|
+
value = "1.39M"
|
|
135
|
+
converted_value = finviz_data._convert_value(value)
|
|
136
|
+
self.assertEqual(converted_value, 1390000)
|
|
137
|
+
|
|
138
|
+
def test_convert_negative_number(self):
|
|
139
|
+
value = "-1.39"
|
|
140
|
+
converted_value = finviz_data._convert_value(value)
|
|
141
|
+
self.assertEqual(converted_value, -1.39)
|
|
142
|
+
|
|
143
|
+
def test_convert_empty_value(self):
|
|
144
|
+
self.assertIsNone(finviz_data._convert_value(""))
|
|
145
|
+
self.assertIsNone(finviz_data._convert_value("-"))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
if __name__ == "__main__":
|
|
149
|
+
unittest.main()
|