bullishpy 0.4.0__tar.gz → 0.5.0__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.
Potentially problematic release.
This version of bullishpy might be problematic. Click here for more details.
- {bullishpy-0.4.0 → bullishpy-0.5.0}/PKG-INFO +2 -2
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/analysis/analysis.py +22 -19
- bullishpy-0.5.0/bullish/analysis/filter.py +583 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/app/app.py +123 -53
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/cli.py +3 -1
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/versions/037dbd721317_.py +1 -0
- bullishpy-0.5.0/bullish/database/alembic/versions/11d35a452b40_.py +368 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/versions/4b0a2f40b7d3_.py +1 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/versions/73564b60fe24_.py +1 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/crud.py +9 -2
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/interface/interface.py +13 -27
- {bullishpy-0.4.0 → bullishpy-0.5.0}/pyproject.toml +2 -2
- bullishpy-0.4.0/bullish/analysis/filter.py +0 -123
- {bullishpy-0.4.0 → bullishpy-0.5.0}/README.md +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/analysis/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/app/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/README +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/alembic.ini +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/env.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/alembic/script.py.mako +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/schemas.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/scripts/create_revision.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/scripts/stamp.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/scripts/upgrade.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/database/settings.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/exceptions.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/figures/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/figures/figures.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/interface/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/jobs/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/jobs/app.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/jobs/models.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.5.0}/bullish/jobs/tasks.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bullishpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary:
|
|
5
5
|
Author: aan
|
|
6
6
|
Author-email: andoludovic.andriamamonjy@gmail.com
|
|
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Requires-Dist: bearishpy (>=0.
|
|
12
|
+
Requires-Dist: bearishpy (>=0.20.0,<0.21.0)
|
|
13
13
|
Requires-Dist: huey (>=2.5.3,<3.0.0)
|
|
14
14
|
Requires-Dist: pandas-ta (>=0.3.14b0,<0.4.0)
|
|
15
15
|
Requires-Dist: plotly (>=6.1.2,<7.0.0)
|
|
@@ -153,7 +153,9 @@ def _abs(data: pd.Series) -> pd.Series: # type: ignore
|
|
|
153
153
|
|
|
154
154
|
|
|
155
155
|
class TechnicalAnalysis(BaseModel):
|
|
156
|
-
rsi_last_value: Optional[float] =
|
|
156
|
+
rsi_last_value: Optional[float] = Field(
|
|
157
|
+
None, alias="RSI Last value", description="RSI last value", ge=0, le=100
|
|
158
|
+
)
|
|
157
159
|
macd_12_26_9_buy_date: Optional[date] = None
|
|
158
160
|
ma_50_200_buy_date: Optional[date] = None
|
|
159
161
|
slope_7: Optional[float] = None
|
|
@@ -332,26 +334,28 @@ class TechnicalAnalysis(BaseModel):
|
|
|
332
334
|
|
|
333
335
|
|
|
334
336
|
class BaseFundamentalAnalysis(BaseModel):
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
positive_debt_to_equity: Optional[bool] = None
|
|
338
|
+
positive_return_on_assets: Optional[bool] = None
|
|
339
|
+
positive_return_on_equity: Optional[bool] = None
|
|
340
|
+
positive_diluted_eps: Optional[bool] = None
|
|
341
|
+
positive_basic_eps: Optional[bool] = None
|
|
342
|
+
growing_basic_eps: Optional[bool] = None
|
|
343
|
+
growing_diluted_eps: Optional[bool] = None
|
|
344
|
+
positive_net_income: Optional[bool] = None
|
|
345
|
+
positive_operating_income: Optional[bool] = None
|
|
346
|
+
growing_net_income: Optional[bool] = None
|
|
347
|
+
growing_operating_income: Optional[bool] = None
|
|
348
|
+
positive_free_cash_flow: Optional[bool] = None
|
|
349
|
+
growing_operating_cash_flow: Optional[bool] = None
|
|
350
|
+
operating_cash_flow_is_higher_than_net_income: Optional[bool] = None
|
|
351
|
+
|
|
338
352
|
mean_capex_ratio: Optional[float] = None
|
|
339
353
|
max_capex_ratio: Optional[float] = None
|
|
340
354
|
min_capex_ratio: Optional[float] = None
|
|
341
355
|
mean_dividend_payout_ratio: Optional[float] = None
|
|
342
356
|
max_dividend_payout_ratio: Optional[float] = None
|
|
343
357
|
min_dividend_payout_ratio: Optional[float] = None
|
|
344
|
-
|
|
345
|
-
positive_operating_income: Optional[float] = None
|
|
346
|
-
growing_net_income: Optional[float] = None
|
|
347
|
-
growing_operating_income: Optional[float] = None
|
|
348
|
-
positive_diluted_eps: Optional[float] = None
|
|
349
|
-
positive_basic_eps: Optional[float] = None
|
|
350
|
-
growing_basic_eps: Optional[float] = None
|
|
351
|
-
growing_diluted_eps: Optional[float] = None
|
|
352
|
-
positive_debt_to_equity: Optional[float] = None
|
|
353
|
-
positive_return_on_assets: Optional[float] = None
|
|
354
|
-
positive_return_on_equity: Optional[float] = None
|
|
358
|
+
|
|
355
359
|
earning_per_share: Optional[float] = None
|
|
356
360
|
|
|
357
361
|
def is_empty(self) -> bool:
|
|
@@ -481,13 +485,12 @@ class BaseFundamentalAnalysis(BaseModel):
|
|
|
481
485
|
return cls()
|
|
482
486
|
|
|
483
487
|
|
|
484
|
-
class YearlyFundamentalAnalysis(BaseFundamentalAnalysis):
|
|
485
|
-
...
|
|
488
|
+
class YearlyFundamentalAnalysis(BaseFundamentalAnalysis): ...
|
|
486
489
|
|
|
487
490
|
|
|
488
491
|
fields_with_prefix = {
|
|
489
|
-
f"{QUARTERLY}_{name}": (
|
|
490
|
-
for name in BaseFundamentalAnalysis.model_fields
|
|
492
|
+
f"{QUARTERLY}_{name}": (field_info.annotation, Field(default=None))
|
|
493
|
+
for name, field_info in BaseFundamentalAnalysis.model_fields.items()
|
|
491
494
|
}
|
|
492
495
|
|
|
493
496
|
# Create the new model
|
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from datetime import date
|
|
3
|
+
from typing import Literal, get_args, Any, Optional, List, Tuple, Type
|
|
4
|
+
|
|
5
|
+
from bearish.types import SeriesLength # type: ignore
|
|
6
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
7
|
+
from pydantic import create_model
|
|
8
|
+
from pydantic.fields import FieldInfo
|
|
9
|
+
|
|
10
|
+
from bullish.analysis.analysis import (
|
|
11
|
+
TechnicalAnalysis,
|
|
12
|
+
YearlyFundamentalAnalysis,
|
|
13
|
+
QuarterlyFundamentalAnalysis,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
Industry = Literal[
|
|
17
|
+
"Publishing",
|
|
18
|
+
"Internet Retail",
|
|
19
|
+
"Scientific & Technical Instruments",
|
|
20
|
+
"Engineering & Construction",
|
|
21
|
+
"Diagnostics & Research",
|
|
22
|
+
"Software - Infrastructure",
|
|
23
|
+
"Thermal Coal",
|
|
24
|
+
"Software - Application",
|
|
25
|
+
"Auto Manufacturers",
|
|
26
|
+
"Farm Products",
|
|
27
|
+
"Medical Devices",
|
|
28
|
+
"Education & Training Services",
|
|
29
|
+
"Auto Parts",
|
|
30
|
+
"Specialty Chemicals",
|
|
31
|
+
"Marine Shipping",
|
|
32
|
+
"Biotechnology",
|
|
33
|
+
"Real Estate Services",
|
|
34
|
+
"Gold",
|
|
35
|
+
"Entertainment",
|
|
36
|
+
"Specialty Retail",
|
|
37
|
+
"Utilities - Independent Power Producers",
|
|
38
|
+
"Steel",
|
|
39
|
+
"Mortgage Finance",
|
|
40
|
+
"Communication Equipment",
|
|
41
|
+
"Drug Manufacturers - Specialty & Generic",
|
|
42
|
+
"Electronic Gaming & Multimedia",
|
|
43
|
+
"Banks - Regional",
|
|
44
|
+
"Oil & Gas E&P",
|
|
45
|
+
"Travel Services",
|
|
46
|
+
"Real Estate - Diversified",
|
|
47
|
+
"Telecom Services",
|
|
48
|
+
"Uranium",
|
|
49
|
+
"Consulting Services",
|
|
50
|
+
"Waste Management",
|
|
51
|
+
"Agricultural Inputs",
|
|
52
|
+
"Utilities - Diversified",
|
|
53
|
+
"Auto & Truck Dealerships",
|
|
54
|
+
"Confectioners",
|
|
55
|
+
"Other Industrial Metals & Mining",
|
|
56
|
+
"Beverages - Wineries & Distilleries",
|
|
57
|
+
"Oil & Gas Midstream",
|
|
58
|
+
"Recreational Vehicles",
|
|
59
|
+
"Electrical Equipment & Parts",
|
|
60
|
+
"Household & Personal Products",
|
|
61
|
+
"Packaging & Containers",
|
|
62
|
+
"REIT - Specialty",
|
|
63
|
+
"Home Improvement Retail",
|
|
64
|
+
"Electronic Components",
|
|
65
|
+
"Asset Management",
|
|
66
|
+
"Consumer Electronics",
|
|
67
|
+
"Conglomerates",
|
|
68
|
+
"Health Information Services",
|
|
69
|
+
"Medical Instruments & Supplies",
|
|
70
|
+
"Building Products & Equipment",
|
|
71
|
+
"Information Technology Services",
|
|
72
|
+
"Specialty Industrial Machinery",
|
|
73
|
+
"Food Distribution",
|
|
74
|
+
"Packaged Foods",
|
|
75
|
+
"Rental & Leasing Services",
|
|
76
|
+
"Medical Distribution",
|
|
77
|
+
"Grocery Stores",
|
|
78
|
+
"Advertising Agencies",
|
|
79
|
+
"Beverages - Non - Alcoholic",
|
|
80
|
+
"Apparel Manufacturing",
|
|
81
|
+
"Oil & Gas Equipment & Services",
|
|
82
|
+
"Coking Coal",
|
|
83
|
+
"Industrial Distribution",
|
|
84
|
+
"Restaurants",
|
|
85
|
+
"Beverages - Brewers",
|
|
86
|
+
"Chemicals",
|
|
87
|
+
"Real Estate - Development",
|
|
88
|
+
"Credit Services",
|
|
89
|
+
"Tobacco",
|
|
90
|
+
"Metal Fabrication",
|
|
91
|
+
"Building Materials",
|
|
92
|
+
"Residential Construction",
|
|
93
|
+
"Specialty Business Services",
|
|
94
|
+
"REIT - Hotel & Motel",
|
|
95
|
+
"Internet Content & Information",
|
|
96
|
+
"Lodging",
|
|
97
|
+
"Furnishings, Fixtures & Appliances",
|
|
98
|
+
"Airlines",
|
|
99
|
+
"Computer Hardware",
|
|
100
|
+
"Integrated Freight & Logistics",
|
|
101
|
+
"Solar",
|
|
102
|
+
"Capital Markets",
|
|
103
|
+
"Leisure",
|
|
104
|
+
"Airports & Air Services",
|
|
105
|
+
"Aluminum",
|
|
106
|
+
"Insurance Brokers",
|
|
107
|
+
"Semiconductors",
|
|
108
|
+
"REIT - Retail",
|
|
109
|
+
"Luxury Goods",
|
|
110
|
+
"Lumber & Wood Production",
|
|
111
|
+
"REIT - Mortgage",
|
|
112
|
+
"Semiconductor Equipment & Materials",
|
|
113
|
+
"Aerospace & Defense",
|
|
114
|
+
"Security & Protection Services",
|
|
115
|
+
"Utilities - Renewable",
|
|
116
|
+
"Utilities - Regulated Gas",
|
|
117
|
+
"Apparel Retail",
|
|
118
|
+
"Pollution & Treatment Controls",
|
|
119
|
+
"Broadcasting",
|
|
120
|
+
"Resorts & Casinos",
|
|
121
|
+
"Other Precious Metals & Mining",
|
|
122
|
+
"Financial Data & Stock Exchanges",
|
|
123
|
+
"Footwear & Accessories",
|
|
124
|
+
"Medical Care Facilities",
|
|
125
|
+
"Electronics & Computer Distribution",
|
|
126
|
+
"Gambling",
|
|
127
|
+
"Tools & Accessories",
|
|
128
|
+
"Insurance - Property & Casualty",
|
|
129
|
+
"Utilities - Regulated Water",
|
|
130
|
+
"Insurance - Specialty",
|
|
131
|
+
"Personal Services",
|
|
132
|
+
"Pharmaceutical Retailers",
|
|
133
|
+
"Farm & Heavy Construction Machinery",
|
|
134
|
+
"Utilities - Regulated Electric",
|
|
135
|
+
"Department Stores",
|
|
136
|
+
"Staffing & Employment Services",
|
|
137
|
+
"Textile Manufacturing",
|
|
138
|
+
"Silver",
|
|
139
|
+
"REIT - Industrial",
|
|
140
|
+
"REIT - Diversified",
|
|
141
|
+
"Copper",
|
|
142
|
+
"Business Equipment & Supplies",
|
|
143
|
+
"Infrastructure Operations",
|
|
144
|
+
"Trucking",
|
|
145
|
+
"Insurance - Reinsurance",
|
|
146
|
+
"Insurance - Diversified",
|
|
147
|
+
"Drug Manufacturers - General",
|
|
148
|
+
"Oil & Gas Drilling",
|
|
149
|
+
"Banks - Diversified",
|
|
150
|
+
"REIT - Residential",
|
|
151
|
+
"Oil & Gas Refining & Marketing",
|
|
152
|
+
"Shell Companies",
|
|
153
|
+
"Financial Conglomerates",
|
|
154
|
+
"Paper & Paper Products",
|
|
155
|
+
"Insurance - Life",
|
|
156
|
+
"REIT - Office",
|
|
157
|
+
"Railroads",
|
|
158
|
+
"Oil & Gas Integrated",
|
|
159
|
+
"Healthcare Plans",
|
|
160
|
+
"REIT - Healthcare Facilities",
|
|
161
|
+
"Discount Stores",
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
IndustryGroup = Literal[
|
|
165
|
+
"publishing",
|
|
166
|
+
"internet-retail",
|
|
167
|
+
"scientific-technical-instruments",
|
|
168
|
+
"engineering-construction",
|
|
169
|
+
"diagnostics-research",
|
|
170
|
+
"software-infrastructure",
|
|
171
|
+
"thermal-coal",
|
|
172
|
+
"software-application",
|
|
173
|
+
"auto-manufacturers",
|
|
174
|
+
"farm-products",
|
|
175
|
+
"medical-devices",
|
|
176
|
+
"education-training-services",
|
|
177
|
+
"auto-parts",
|
|
178
|
+
"specialty-chemicals",
|
|
179
|
+
"marine-shipping",
|
|
180
|
+
"biotechnology",
|
|
181
|
+
"real-estate-services",
|
|
182
|
+
"gold",
|
|
183
|
+
"entertainment",
|
|
184
|
+
"specialty-retail",
|
|
185
|
+
"utilities-independent-power-producers",
|
|
186
|
+
"steel",
|
|
187
|
+
"mortgage-finance",
|
|
188
|
+
"communication-equipment",
|
|
189
|
+
"drug-manufacturers-specialty-generic",
|
|
190
|
+
"electronic-gaming-multimedia",
|
|
191
|
+
"banks-regional",
|
|
192
|
+
"oil-gas-e-p",
|
|
193
|
+
"travel-services",
|
|
194
|
+
"real-estate-diversified",
|
|
195
|
+
"telecom-services",
|
|
196
|
+
"uranium",
|
|
197
|
+
"consulting-services",
|
|
198
|
+
"waste-management",
|
|
199
|
+
"agricultural-inputs",
|
|
200
|
+
"utilities-diversified",
|
|
201
|
+
"auto-truck-dealerships",
|
|
202
|
+
"confectioners",
|
|
203
|
+
"other-industrial-metals-mining",
|
|
204
|
+
"beverages-wineries-distilleries",
|
|
205
|
+
"oil-gas-midstream",
|
|
206
|
+
"recreational-vehicles",
|
|
207
|
+
"electrical-equipment-parts",
|
|
208
|
+
"household-personal-products",
|
|
209
|
+
"packaging-containers",
|
|
210
|
+
"reit-specialty",
|
|
211
|
+
"home-improvement-retail",
|
|
212
|
+
"electronic-components",
|
|
213
|
+
"asset-management",
|
|
214
|
+
"consumer-electronics",
|
|
215
|
+
"conglomerates",
|
|
216
|
+
"health-information-services",
|
|
217
|
+
"medical-instruments-supplies",
|
|
218
|
+
"building-products-equipment",
|
|
219
|
+
"information-technology-services",
|
|
220
|
+
"specialty-industrial-machinery",
|
|
221
|
+
"food-distribution",
|
|
222
|
+
"packaged-foods",
|
|
223
|
+
"rental-leasing-services",
|
|
224
|
+
"medical-distribution",
|
|
225
|
+
"grocery-stores",
|
|
226
|
+
"advertising-agencies",
|
|
227
|
+
"beverages-non-alcoholic",
|
|
228
|
+
"apparel-manufacturing",
|
|
229
|
+
"oil-gas-equipment-services",
|
|
230
|
+
"coking-coal",
|
|
231
|
+
"industrial-distribution",
|
|
232
|
+
"restaurants",
|
|
233
|
+
"beverages-brewers",
|
|
234
|
+
"chemicals",
|
|
235
|
+
"real-estate-development",
|
|
236
|
+
"credit-services",
|
|
237
|
+
"tobacco",
|
|
238
|
+
"metal-fabrication",
|
|
239
|
+
"building-materials",
|
|
240
|
+
"residential-construction",
|
|
241
|
+
"specialty-business-services",
|
|
242
|
+
"reit-hotel-motel",
|
|
243
|
+
"internet-content-information",
|
|
244
|
+
"lodging",
|
|
245
|
+
"furnishings-fixtures-appliances",
|
|
246
|
+
"airlines",
|
|
247
|
+
"computer-hardware",
|
|
248
|
+
"integrated-freight-logistics",
|
|
249
|
+
"solar",
|
|
250
|
+
"capital-markets",
|
|
251
|
+
"leisure",
|
|
252
|
+
"airports-air-services",
|
|
253
|
+
"aluminum",
|
|
254
|
+
"insurance-brokers",
|
|
255
|
+
"semiconductors",
|
|
256
|
+
"reit-retail",
|
|
257
|
+
"luxury-goods",
|
|
258
|
+
"lumber-wood-production",
|
|
259
|
+
"reit-mortgage",
|
|
260
|
+
"semiconductor-equipment-materials",
|
|
261
|
+
"aerospace-defense",
|
|
262
|
+
"security-protection-services",
|
|
263
|
+
"utilities-renewable",
|
|
264
|
+
"utilities-regulated-gas",
|
|
265
|
+
"apparel-retail",
|
|
266
|
+
"pollution-treatment-controls",
|
|
267
|
+
"broadcasting",
|
|
268
|
+
"resorts-casinos",
|
|
269
|
+
"other-precious-metals-mining",
|
|
270
|
+
"financial-data-stock-exchanges",
|
|
271
|
+
"footwear-accessories",
|
|
272
|
+
"medical-care-facilities",
|
|
273
|
+
"electronics-computer-distribution",
|
|
274
|
+
"gambling",
|
|
275
|
+
"tools-accessories",
|
|
276
|
+
"insurance-property-casualty",
|
|
277
|
+
"utilities-regulated-water",
|
|
278
|
+
"insurance-specialty",
|
|
279
|
+
"personal-services",
|
|
280
|
+
"pharmaceutical-retailers",
|
|
281
|
+
"farm-heavy-construction-machinery",
|
|
282
|
+
"utilities-regulated-electric",
|
|
283
|
+
"department-stores",
|
|
284
|
+
"staffing-employment-services",
|
|
285
|
+
"textile-manufacturing",
|
|
286
|
+
"silver",
|
|
287
|
+
"reit-industrial",
|
|
288
|
+
"reit-diversified",
|
|
289
|
+
"copper",
|
|
290
|
+
"business-equipment-supplies",
|
|
291
|
+
"infrastructure-operations",
|
|
292
|
+
"trucking",
|
|
293
|
+
"insurance-reinsurance",
|
|
294
|
+
"insurance-diversified",
|
|
295
|
+
"drug-manufacturers-general",
|
|
296
|
+
"oil-gas-drilling",
|
|
297
|
+
"banks-diversified",
|
|
298
|
+
"reit-residential",
|
|
299
|
+
"oil-gas-refining-marketing",
|
|
300
|
+
"shell-companies",
|
|
301
|
+
"financial-conglomerates",
|
|
302
|
+
"paper-paper-products",
|
|
303
|
+
"insurance-life",
|
|
304
|
+
"reit-office",
|
|
305
|
+
"railroads",
|
|
306
|
+
"oil-gas-integrated",
|
|
307
|
+
"healthcare-plans",
|
|
308
|
+
"reit-healthcare-facilities",
|
|
309
|
+
"discount-stores",
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
Sector = Literal[
|
|
313
|
+
"Communication Services",
|
|
314
|
+
"Consumer Cyclical",
|
|
315
|
+
"Technology",
|
|
316
|
+
"Industrials",
|
|
317
|
+
"Healthcare",
|
|
318
|
+
"Energy",
|
|
319
|
+
"Consumer Defensive",
|
|
320
|
+
"Basic Materials",
|
|
321
|
+
"Real Estate",
|
|
322
|
+
"Utilities",
|
|
323
|
+
"Financial Services",
|
|
324
|
+
"Conglomerates",
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
Country = Literal[
|
|
328
|
+
"Australia",
|
|
329
|
+
"China",
|
|
330
|
+
"Japan",
|
|
331
|
+
"United kingdom",
|
|
332
|
+
"United states",
|
|
333
|
+
"Poland",
|
|
334
|
+
"Switzerland",
|
|
335
|
+
"Canada",
|
|
336
|
+
"Greece",
|
|
337
|
+
"Spain",
|
|
338
|
+
"Germany",
|
|
339
|
+
"Indonesia",
|
|
340
|
+
"Belgium",
|
|
341
|
+
"France",
|
|
342
|
+
"Netherlands",
|
|
343
|
+
"British virgin islands",
|
|
344
|
+
"Italy",
|
|
345
|
+
"Hungary",
|
|
346
|
+
"Austria",
|
|
347
|
+
"Finland",
|
|
348
|
+
"Sweden",
|
|
349
|
+
"Bermuda",
|
|
350
|
+
"Taiwan",
|
|
351
|
+
"Israel",
|
|
352
|
+
"Ukraine",
|
|
353
|
+
"Singapore",
|
|
354
|
+
"Jersey",
|
|
355
|
+
"Ireland",
|
|
356
|
+
"Luxembourg",
|
|
357
|
+
"Cyprus",
|
|
358
|
+
"Cayman islands",
|
|
359
|
+
"Norway",
|
|
360
|
+
"Denmark",
|
|
361
|
+
"Hong kong",
|
|
362
|
+
"New zealand",
|
|
363
|
+
"Kazakhstan",
|
|
364
|
+
"Nigeria",
|
|
365
|
+
"Argentina",
|
|
366
|
+
"Brazil",
|
|
367
|
+
"Czech republic",
|
|
368
|
+
"Mauritius",
|
|
369
|
+
"South africa",
|
|
370
|
+
"India",
|
|
371
|
+
"Mexico",
|
|
372
|
+
"Mongolia",
|
|
373
|
+
"Slovenia",
|
|
374
|
+
"Thailand",
|
|
375
|
+
"Malaysia",
|
|
376
|
+
"Costa rica",
|
|
377
|
+
"Isle of man",
|
|
378
|
+
"Egypt",
|
|
379
|
+
"Turkey",
|
|
380
|
+
"United arab emirates",
|
|
381
|
+
"Colombia",
|
|
382
|
+
"Gibraltar",
|
|
383
|
+
"Malta",
|
|
384
|
+
"Liechtenstein",
|
|
385
|
+
"Guernsey",
|
|
386
|
+
"Peru",
|
|
387
|
+
"Estonia",
|
|
388
|
+
"French guiana",
|
|
389
|
+
"Portugal",
|
|
390
|
+
"Uruguay",
|
|
391
|
+
"Chile",
|
|
392
|
+
"Martinique",
|
|
393
|
+
"Monaco",
|
|
394
|
+
"Panama",
|
|
395
|
+
"Papua new guinea",
|
|
396
|
+
"South korea",
|
|
397
|
+
"Macau",
|
|
398
|
+
"Gabon",
|
|
399
|
+
"Romania",
|
|
400
|
+
"Senegal",
|
|
401
|
+
"Morocco",
|
|
402
|
+
"Jordan",
|
|
403
|
+
"Lithuania",
|
|
404
|
+
"Dominican republic",
|
|
405
|
+
"Reunion",
|
|
406
|
+
"Zambia",
|
|
407
|
+
"Cambodia",
|
|
408
|
+
"Myanmar",
|
|
409
|
+
"Bahamas",
|
|
410
|
+
"Philippines",
|
|
411
|
+
"Bangladesh",
|
|
412
|
+
"Latvia",
|
|
413
|
+
"Vietnam",
|
|
414
|
+
"Iceland",
|
|
415
|
+
"Azerbaijan",
|
|
416
|
+
"Georgia",
|
|
417
|
+
"Liberia",
|
|
418
|
+
"Kenya",
|
|
419
|
+
]
|
|
420
|
+
SIZE_RANGE = 2
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _get_type(name: str, info: FieldInfo) -> Tuple[Any, Any]:
|
|
424
|
+
alias = info.alias or " ".join(name.capitalize().split("_")).strip()
|
|
425
|
+
if info.annotation == Optional[float]: # type: ignore
|
|
426
|
+
ge = next((item.ge for item in info.metadata if hasattr(item, "ge")), 0)
|
|
427
|
+
le = next((item.le for item in info.metadata if hasattr(item, "le")), 100)
|
|
428
|
+
return (Optional[List[float]], Field(default=[ge, le], alias=alias))
|
|
429
|
+
elif info.annotation == Optional[date]: # type: ignore
|
|
430
|
+
le = date.today()
|
|
431
|
+
ge = le - datetime.timedelta(days=30 * 12) # 30 days * 12 months
|
|
432
|
+
return (List[date], Field(default=[ge, le], alias=alias))
|
|
433
|
+
else:
|
|
434
|
+
raise NotImplementedError
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
FUNDAMENTAL_ANALYSIS_GROUP = ["income", "cash_flow", "eps"]
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _get_fundamental_analysis_boolean_fields() -> List[str]:
|
|
441
|
+
return [
|
|
442
|
+
name
|
|
443
|
+
for name, info in {
|
|
444
|
+
**YearlyFundamentalAnalysis.model_fields,
|
|
445
|
+
**QuarterlyFundamentalAnalysis.model_fields,
|
|
446
|
+
}.items()
|
|
447
|
+
if info.annotation == Optional[bool]
|
|
448
|
+
]
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def get_boolean_field_group(group: str) -> List[str]:
|
|
452
|
+
groups = FUNDAMENTAL_ANALYSIS_GROUP.copy()
|
|
453
|
+
groups.remove(group)
|
|
454
|
+
return [
|
|
455
|
+
name
|
|
456
|
+
for name in _get_fundamental_analysis_boolean_fields()
|
|
457
|
+
if group in name and not any(g in name for g in groups)
|
|
458
|
+
]
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
INCOME_GROUP = get_boolean_field_group("income")
|
|
462
|
+
CASH_FLOW_GROUP = get_boolean_field_group("cash_flow")
|
|
463
|
+
EPS_GROUP = get_boolean_field_group("eps")
|
|
464
|
+
PROPERTIES_GROUP = list(
|
|
465
|
+
set(_get_fundamental_analysis_boolean_fields()).difference(
|
|
466
|
+
{*INCOME_GROUP, *CASH_FLOW_GROUP, *EPS_GROUP}
|
|
467
|
+
)
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
GROUP_MAPPING = {
|
|
471
|
+
"income": INCOME_GROUP,
|
|
472
|
+
"cash_flow": CASH_FLOW_GROUP,
|
|
473
|
+
"eps": EPS_GROUP,
|
|
474
|
+
"properties": PROPERTIES_GROUP,
|
|
475
|
+
"country": get_args(Country),
|
|
476
|
+
"industry": get_args(Industry),
|
|
477
|
+
"industry_group": get_args(IndustryGroup),
|
|
478
|
+
"sector": get_args(Sector),
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _create_fundamental_analysis_model() -> Type[BaseModel]:
|
|
483
|
+
boolean_fields = {
|
|
484
|
+
"income": (Optional[List[str]], Field(default=None)),
|
|
485
|
+
"cash_flow": (Optional[List[str]], Field(default=None)),
|
|
486
|
+
"eps": (Optional[List[str]], Field(default=None)),
|
|
487
|
+
"properties": (Optional[List[str]], Field(default=None)),
|
|
488
|
+
}
|
|
489
|
+
remaining_fields = {
|
|
490
|
+
name: _get_type(name, info)
|
|
491
|
+
for name, info in {
|
|
492
|
+
**YearlyFundamentalAnalysis.model_fields,
|
|
493
|
+
**QuarterlyFundamentalAnalysis.model_fields,
|
|
494
|
+
}.items()
|
|
495
|
+
if info.annotation != Optional[bool]
|
|
496
|
+
}
|
|
497
|
+
return create_model(
|
|
498
|
+
"FundamentalAnalysisFilter",
|
|
499
|
+
__config__=ConfigDict(populate_by_name=True),
|
|
500
|
+
**(boolean_fields | remaining_fields),
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
TechnicalAnalysisFilter = create_model( # type: ignore
|
|
505
|
+
"TechnicalAnalysisFilter",
|
|
506
|
+
__config__=ConfigDict(populate_by_name=True),
|
|
507
|
+
**{
|
|
508
|
+
name: _get_type(name, info)
|
|
509
|
+
for name, info in TechnicalAnalysis.model_fields.items()
|
|
510
|
+
},
|
|
511
|
+
)
|
|
512
|
+
FundamentalAnalysisFilter = _create_fundamental_analysis_model()
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class GeneralFilter(BaseModel):
|
|
516
|
+
country: Optional[List[str]] = None
|
|
517
|
+
industry: Optional[List[str]] = None
|
|
518
|
+
industry_group: Optional[List[str]] = None
|
|
519
|
+
sector: Optional[List[str]] = None
|
|
520
|
+
market_capitalization: Optional[List[float]] = Field(
|
|
521
|
+
default_factory=lambda: [5e8, 1e12]
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class FilterQuery(GeneralFilter, TechnicalAnalysisFilter, FundamentalAnalysisFilter): # type: ignore
|
|
526
|
+
|
|
527
|
+
def valid(self) -> bool:
|
|
528
|
+
return bool(self.model_dump(exclude_defaults=True, exclude_unset=True))
|
|
529
|
+
|
|
530
|
+
def to_query(self) -> str:
|
|
531
|
+
parameters = self.model_dump(exclude_defaults=True, exclude_unset=True)
|
|
532
|
+
query = []
|
|
533
|
+
for parameter, value in parameters.items():
|
|
534
|
+
if not value:
|
|
535
|
+
continue
|
|
536
|
+
|
|
537
|
+
if (
|
|
538
|
+
isinstance(value, list)
|
|
539
|
+
and all(isinstance(item, str) for item in value)
|
|
540
|
+
and parameter not in GeneralFilter.model_fields
|
|
541
|
+
):
|
|
542
|
+
query.append(" AND ".join([f"{v}=1" for v in value]))
|
|
543
|
+
elif (
|
|
544
|
+
isinstance(value, list)
|
|
545
|
+
and len(value) == SIZE_RANGE
|
|
546
|
+
and all(isinstance(item, (int, float)) for item in value)
|
|
547
|
+
):
|
|
548
|
+
query.append(f"{parameter} BETWEEN {value[0]} AND {value[1]}")
|
|
549
|
+
elif (
|
|
550
|
+
isinstance(value, list)
|
|
551
|
+
and len(value) == SIZE_RANGE
|
|
552
|
+
and all(isinstance(item, date) for item in value)
|
|
553
|
+
):
|
|
554
|
+
query.append(f"{parameter} BETWEEN '{value[0]}' AND '{value[1]}'")
|
|
555
|
+
elif (
|
|
556
|
+
isinstance(value, list)
|
|
557
|
+
and all(isinstance(item, str) for item in value)
|
|
558
|
+
and parameter in GeneralFilter.model_fields
|
|
559
|
+
):
|
|
560
|
+
general_filters = [f"'{v}'" for v in value]
|
|
561
|
+
query.append(f"{parameter} IN ({', '.join(general_filters)})")
|
|
562
|
+
else:
|
|
563
|
+
raise NotImplementedError
|
|
564
|
+
query_ = " AND ".join(query)
|
|
565
|
+
return query_
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class FilterQueryStored(FilterQuery): ...
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class FilterUpdate(BaseModel):
|
|
572
|
+
window_size: SeriesLength = Field("5d")
|
|
573
|
+
data_age_in_days: int = 1
|
|
574
|
+
update_financials: bool = False
|
|
575
|
+
update_analysis_only: bool = False
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
class FilteredResults(BaseModel):
|
|
579
|
+
name: str
|
|
580
|
+
filter_query: FilterQueryStored
|
|
581
|
+
symbols: list[str] = Field(
|
|
582
|
+
default_factory=list, description="List of filtered tickers."
|
|
583
|
+
)
|