bullishpy 0.4.0__tar.gz → 0.6.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.6.0}/PKG-INFO +4 -5
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/analysis/analysis.py +114 -250
- bullishpy-0.6.0/bullish/analysis/filter.py +625 -0
- bullishpy-0.6.0/bullish/analysis/functions.py +344 -0
- bullishpy-0.6.0/bullish/analysis/indicators.py +450 -0
- bullishpy-0.6.0/bullish/analysis/predefined_filters.py +87 -0
- bullishpy-0.6.0/bullish/app/app.py +370 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/cli.py +3 -1
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/versions/037dbd721317_.py +1 -0
- bullishpy-0.6.0/bullish/database/alembic/versions/08ac1116e055_.py +592 -0
- bullishpy-0.6.0/bullish/database/alembic/versions/11d35a452b40_.py +368 -0
- bullishpy-0.6.0/bullish/database/alembic/versions/49c83f9eb5ac_.py +103 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/versions/4b0a2f40b7d3_.py +1 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/versions/73564b60fe24_.py +1 -0
- bullishpy-0.6.0/bullish/database/alembic/versions/ee5baabb35f8_.py +51 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/crud.py +14 -2
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/schemas.py +13 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/figures/figures.py +52 -12
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/interface/interface.py +16 -27
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/jobs/tasks.py +10 -0
- bullishpy-0.6.0/bullish/utils/__init__.py +0 -0
- bullishpy-0.6.0/bullish/utils/checks.py +64 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/pyproject.toml +8 -6
- bullishpy-0.4.0/bullish/analysis/filter.py +0 -123
- bullishpy-0.4.0/bullish/app/app.py +0 -218
- {bullishpy-0.4.0 → bullishpy-0.6.0}/README.md +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/analysis/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/app/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/README +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/alembic.ini +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/env.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/alembic/script.py.mako +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/scripts/create_revision.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/scripts/stamp.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/scripts/upgrade.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/database/settings.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/exceptions.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/figures/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/interface/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/jobs/__init__.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/jobs/app.py +0 -0
- {bullishpy-0.4.0 → bullishpy-0.6.0}/bullish/jobs/models.py +0 -0
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bullishpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary:
|
|
5
5
|
Author: aan
|
|
6
6
|
Author-email: andoludovic.andriamamonjy@gmail.com
|
|
7
|
-
Requires-Python: >=3.
|
|
7
|
+
Requires-Python: >=3.12,<3.13
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
-
Requires-Dist: bearishpy (>=0.
|
|
10
|
+
Requires-Dist: bearishpy (>=0.20.0,<0.21.0)
|
|
13
11
|
Requires-Dist: huey (>=2.5.3,<3.0.0)
|
|
14
12
|
Requires-Dist: pandas-ta (>=0.3.14b0,<0.4.0)
|
|
15
13
|
Requires-Dist: plotly (>=6.1.2,<7.0.0)
|
|
16
14
|
Requires-Dist: streamlit (>=1.45.1,<2.0.0)
|
|
17
15
|
Requires-Dist: streamlit-file-browser (>=3.2.22,<4.0.0)
|
|
18
16
|
Requires-Dist: streamlit-pydantic (>=v0.6.1-rc.3,<0.7.0)
|
|
17
|
+
Requires-Dist: ta-lib (>=0.6.4,<0.7.0)
|
|
19
18
|
Requires-Dist: tickermood (>=0.4.0,<0.5.0)
|
|
20
19
|
Description-Content-Type: text/markdown
|
|
21
20
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from datetime import date
|
|
3
2
|
from typing import (
|
|
4
3
|
Annotated,
|
|
5
4
|
Any,
|
|
@@ -7,13 +6,12 @@ from typing import (
|
|
|
7
6
|
Optional,
|
|
8
7
|
Sequence,
|
|
9
8
|
Type,
|
|
10
|
-
cast,
|
|
11
9
|
get_args,
|
|
12
10
|
TYPE_CHECKING,
|
|
11
|
+
ClassVar,
|
|
13
12
|
)
|
|
14
13
|
|
|
15
14
|
import pandas as pd
|
|
16
|
-
import pandas_ta as ta # type: ignore
|
|
17
15
|
from bearish.interface.interface import BearishDbBase # type: ignore
|
|
18
16
|
from bearish.models.assets.equity import BaseEquity # type: ignore
|
|
19
17
|
from bearish.models.base import ( # type: ignore
|
|
@@ -41,6 +39,8 @@ from bearish.models.query.query import AssetQuery, Symbols # type: ignore
|
|
|
41
39
|
from bearish.types import TickerOnlySources # type: ignore
|
|
42
40
|
from pydantic import BaseModel, BeforeValidator, Field, create_model
|
|
43
41
|
|
|
42
|
+
from bullish.analysis.indicators import Indicators, IndicatorModels
|
|
43
|
+
|
|
44
44
|
if TYPE_CHECKING:
|
|
45
45
|
from bullish.database.crud import BullishDb
|
|
46
46
|
|
|
@@ -61,59 +61,6 @@ def to_float(value: Any) -> Optional[float]:
|
|
|
61
61
|
return float(value)
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def price_growth(prices: pd.DataFrame, days: int, max: bool = False) -> Optional[float]:
|
|
65
|
-
prices_ = prices.copy()
|
|
66
|
-
last_index = prices_.last_valid_index()
|
|
67
|
-
delta = pd.Timedelta(days=days)
|
|
68
|
-
start_index = last_index - delta # type: ignore
|
|
69
|
-
|
|
70
|
-
try:
|
|
71
|
-
closest_index = prices_.index.unique().asof(start_index) # type: ignore
|
|
72
|
-
price = (
|
|
73
|
-
prices_.loc[closest_index].close
|
|
74
|
-
if not max
|
|
75
|
-
else prices_[closest_index:].close.max()
|
|
76
|
-
)
|
|
77
|
-
except Exception as e:
|
|
78
|
-
logger.warning(
|
|
79
|
-
f"""Failing to calculate price growth: {e}.""",
|
|
80
|
-
exc_info=True,
|
|
81
|
-
)
|
|
82
|
-
return None
|
|
83
|
-
return ( # type: ignore
|
|
84
|
-
(prices_.loc[last_index].close - price) * 100 / prices_.loc[last_index].close
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def buy_opportunity(
|
|
89
|
-
series_a: pd.Series, series_b: pd.Series # type: ignore
|
|
90
|
-
) -> Optional[date]:
|
|
91
|
-
sell = ta.cross(series_a=series_a, series_b=series_b)
|
|
92
|
-
buy = ta.cross(series_a=series_b, series_b=series_a)
|
|
93
|
-
if not buy[buy == 1].index.empty and not sell[sell == 1].index.empty:
|
|
94
|
-
last_buy_signal = pd.Timestamp(buy[buy == 1].index[-1])
|
|
95
|
-
last_sell_signal = pd.Timestamp(sell[sell == 1].index[-1])
|
|
96
|
-
if last_buy_signal > last_sell_signal:
|
|
97
|
-
return last_buy_signal
|
|
98
|
-
return None
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def perc(data: pd.Series) -> float: # type: ignore
|
|
102
|
-
return cast(float, ((data.iloc[-1] - data.iloc[0]) / data.iloc[0]) * 100)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def yoy(prices: pd.DataFrame) -> pd.Series: # type: ignore
|
|
106
|
-
return prices.close.resample("YE").apply(perc) # type: ignore
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def mom(prices: pd.DataFrame) -> pd.Series: # type: ignore
|
|
110
|
-
return prices.close.resample("ME").apply(perc) # type: ignore
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def wow(prices: pd.DataFrame) -> pd.Series: # type: ignore
|
|
114
|
-
return prices.close.resample("W").apply(perc) # type: ignore
|
|
115
|
-
|
|
116
|
-
|
|
117
64
|
def _load_data(
|
|
118
65
|
data: Sequence[DataSourceBase], symbol: str, class_: Type[DataSourceBase]
|
|
119
66
|
) -> pd.DataFrame:
|
|
@@ -128,23 +75,23 @@ def _load_data(
|
|
|
128
75
|
return pd.DataFrame(columns=columns).sort_index()
|
|
129
76
|
|
|
130
77
|
|
|
131
|
-
def _compute_growth(series: pd.Series) -> bool:
|
|
78
|
+
def _compute_growth(series: pd.Series) -> bool:
|
|
132
79
|
if series.empty:
|
|
133
80
|
return False
|
|
134
81
|
return all(series.pct_change(fill_method=None).dropna() > 0)
|
|
135
82
|
|
|
136
83
|
|
|
137
|
-
def _all_positive(series: pd.Series) -> bool:
|
|
84
|
+
def _all_positive(series: pd.Series) -> bool:
|
|
138
85
|
if series.empty:
|
|
139
86
|
return False
|
|
140
87
|
return all(series.dropna() > 0)
|
|
141
88
|
|
|
142
89
|
|
|
143
|
-
def _get_last(data: pd.Series) -> Optional[float]:
|
|
90
|
+
def _get_last(data: pd.Series) -> Optional[float]:
|
|
144
91
|
return data.iloc[-1] if not data.empty else None
|
|
145
92
|
|
|
146
93
|
|
|
147
|
-
def _abs(data: pd.Series) -> pd.Series:
|
|
94
|
+
def _abs(data: pd.Series) -> pd.Series:
|
|
148
95
|
try:
|
|
149
96
|
return abs(data)
|
|
150
97
|
except Exception as e:
|
|
@@ -152,17 +99,8 @@ def _abs(data: pd.Series) -> pd.Series: # type: ignore
|
|
|
152
99
|
return data
|
|
153
100
|
|
|
154
101
|
|
|
155
|
-
class
|
|
156
|
-
|
|
157
|
-
macd_12_26_9_buy_date: Optional[date] = None
|
|
158
|
-
ma_50_200_buy_date: Optional[date] = None
|
|
159
|
-
slope_7: Optional[float] = None
|
|
160
|
-
slope_14: Optional[float] = None
|
|
161
|
-
slope_30: Optional[float] = None
|
|
162
|
-
slope_60: Optional[float] = None
|
|
163
|
-
last_adx: Optional[float] = None
|
|
164
|
-
last_dmp: Optional[float] = None
|
|
165
|
-
last_dmn: Optional[float] = None
|
|
102
|
+
class TechnicalAnalysisBase(BaseModel):
|
|
103
|
+
_description: ClassVar[str] = "General technical indicators"
|
|
166
104
|
last_price: Annotated[
|
|
167
105
|
Optional[float],
|
|
168
106
|
BeforeValidator(to_float),
|
|
@@ -170,189 +108,113 @@ class TechnicalAnalysis(BaseModel):
|
|
|
170
108
|
default=None,
|
|
171
109
|
),
|
|
172
110
|
]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
year_to_date_growth: Annotated[
|
|
180
|
-
Optional[float],
|
|
181
|
-
Field(
|
|
182
|
-
default=None,
|
|
183
|
-
),
|
|
184
|
-
]
|
|
185
|
-
last_52_weeks_growth: Annotated[
|
|
186
|
-
Optional[float],
|
|
187
|
-
Field(
|
|
188
|
-
default=None,
|
|
189
|
-
),
|
|
190
|
-
]
|
|
191
|
-
last_week_growth: Annotated[
|
|
192
|
-
Optional[float],
|
|
193
|
-
Field(
|
|
194
|
-
default=None,
|
|
195
|
-
),
|
|
196
|
-
]
|
|
197
|
-
last_month_growth: Annotated[
|
|
198
|
-
Optional[float],
|
|
199
|
-
Field(
|
|
200
|
-
default=None,
|
|
201
|
-
),
|
|
202
|
-
]
|
|
203
|
-
last_year_growth: Annotated[
|
|
204
|
-
Optional[float],
|
|
205
|
-
Field(
|
|
206
|
-
default=None,
|
|
207
|
-
),
|
|
208
|
-
]
|
|
209
|
-
year_to_date_max_growth: Annotated[
|
|
210
|
-
Optional[float],
|
|
211
|
-
Field(
|
|
212
|
-
default=None,
|
|
213
|
-
),
|
|
214
|
-
]
|
|
215
|
-
last_week_max_growth: Annotated[
|
|
216
|
-
Optional[float],
|
|
217
|
-
Field(
|
|
218
|
-
default=None,
|
|
219
|
-
),
|
|
220
|
-
]
|
|
221
|
-
last_month_max_growth: Annotated[
|
|
222
|
-
Optional[float],
|
|
223
|
-
Field(
|
|
224
|
-
default=None,
|
|
225
|
-
),
|
|
226
|
-
]
|
|
227
|
-
last_year_max_growth: Annotated[
|
|
228
|
-
Optional[float],
|
|
229
|
-
Field(
|
|
230
|
-
default=None,
|
|
231
|
-
),
|
|
232
|
-
]
|
|
233
|
-
macd_12_26_9_buy: Annotated[
|
|
234
|
-
Optional[float],
|
|
235
|
-
Field(
|
|
236
|
-
default=None,
|
|
237
|
-
),
|
|
238
|
-
]
|
|
239
|
-
star_yoy: Annotated[
|
|
240
|
-
Optional[float],
|
|
241
|
-
Field(
|
|
242
|
-
default=None,
|
|
243
|
-
),
|
|
244
|
-
]
|
|
245
|
-
star_wow: Annotated[
|
|
246
|
-
Optional[float],
|
|
247
|
-
Field(
|
|
248
|
-
default=None,
|
|
249
|
-
),
|
|
250
|
-
]
|
|
251
|
-
star_mom: Annotated[
|
|
252
|
-
Optional[float],
|
|
253
|
-
Field(
|
|
254
|
-
default=None,
|
|
255
|
-
),
|
|
256
|
-
]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
TechnicalAnalysisModels = [*IndicatorModels, TechnicalAnalysisBase]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class TechnicalAnalysis(*TechnicalAnalysisModels): # type: ignore
|
|
257
117
|
|
|
258
118
|
@classmethod
|
|
259
119
|
def from_data(cls, prices: pd.DataFrame) -> "TechnicalAnalysis":
|
|
260
120
|
try:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
last_index
|
|
264
|
-
- pd.Timestamp(year=last_index.year, month=1, day=1, tz="UTC") # type: ignore
|
|
265
|
-
).days
|
|
266
|
-
year_to_date_growth = price_growth(prices, year_to_date_days)
|
|
267
|
-
last_52_weeks_growth = price_growth(prices=prices, days=399)
|
|
268
|
-
last_week_growth = price_growth(prices=prices, days=7)
|
|
269
|
-
last_month_growth = price_growth(prices=prices, days=31)
|
|
270
|
-
last_year_growth = price_growth(prices=prices, days=365)
|
|
271
|
-
year_to_date_max_growth = price_growth(prices, year_to_date_days, max=True)
|
|
272
|
-
last_week_max_growth = price_growth(prices=prices, days=7, max=True)
|
|
273
|
-
last_month_max_growth = price_growth(prices=prices, days=31, max=True)
|
|
274
|
-
last_year_max_growth = price_growth(prices=prices, days=365, max=True)
|
|
275
|
-
prices.ta.sma(50, append=True)
|
|
276
|
-
prices.ta.sma(200, append=True)
|
|
277
|
-
prices.ta.adx(append=True)
|
|
278
|
-
prices["SLOPE_14"] = ta.linreg(prices.close, slope=True, length=14)
|
|
279
|
-
prices["SLOPE_7"] = ta.linreg(prices.close, slope=True, length=7)
|
|
280
|
-
prices["SLOPE_30"] = ta.linreg(prices.close, slope=True, length=30)
|
|
281
|
-
prices["SLOPE_60"] = ta.linreg(prices.close, slope=True, length=60)
|
|
282
|
-
prices.ta.macd(append=True)
|
|
283
|
-
prices.ta.rsi(append=True)
|
|
284
|
-
|
|
285
|
-
rsi_last_value = prices.RSI_14.iloc[-1]
|
|
286
|
-
macd_12_26_9_buy_date = buy_opportunity(
|
|
287
|
-
prices.MACDs_12_26_9, prices.MACD_12_26_9
|
|
288
|
-
)
|
|
289
|
-
star_yoy = yoy(prices).median()
|
|
290
|
-
star_mom = mom(prices).median()
|
|
291
|
-
star_wow = wow(prices).median()
|
|
292
|
-
try:
|
|
293
|
-
macd_12_26_9_buy = (
|
|
294
|
-
prices.MACD_12_26_9.iloc[-1] > prices.MACDs_12_26_9.iloc[-1]
|
|
295
|
-
)
|
|
296
|
-
except Exception as e:
|
|
297
|
-
logger.warning(
|
|
298
|
-
f"Failing to calculate MACD buy date: {e}", exc_info=True
|
|
299
|
-
)
|
|
300
|
-
macd_12_26_9_buy = None
|
|
301
|
-
ma_50_200_buy_date = buy_opportunity(prices.SMA_200, prices.SMA_50)
|
|
302
|
-
return cls(
|
|
303
|
-
rsi_last_value=rsi_last_value,
|
|
304
|
-
macd_12_26_9_buy_date=macd_12_26_9_buy_date,
|
|
305
|
-
macd_12_26_9_buy=macd_12_26_9_buy,
|
|
306
|
-
ma_50_200_buy_date=ma_50_200_buy_date,
|
|
307
|
-
last_price=prices.close.iloc[-1],
|
|
308
|
-
last_price_date=prices.index[-1],
|
|
309
|
-
last_adx=prices.ADX_14.iloc[-1],
|
|
310
|
-
last_dmp=prices.DMP_14.iloc[-1],
|
|
311
|
-
last_dmn=prices.DMN_14.iloc[-1],
|
|
312
|
-
slope_7=prices.SLOPE_7.iloc[-1],
|
|
313
|
-
slope_14=prices.SLOPE_14.iloc[-1],
|
|
314
|
-
slope_30=prices.SLOPE_30.iloc[-1],
|
|
315
|
-
slope_60=prices.SLOPE_60.iloc[-1],
|
|
316
|
-
year_to_date_growth=year_to_date_growth,
|
|
317
|
-
last_52_weeks_growth=last_52_weeks_growth,
|
|
318
|
-
last_week_growth=last_week_growth,
|
|
319
|
-
last_month_growth=last_month_growth,
|
|
320
|
-
last_year_growth=last_year_growth,
|
|
321
|
-
year_to_date_max_growth=year_to_date_max_growth,
|
|
322
|
-
last_week_max_growth=last_week_max_growth,
|
|
323
|
-
last_month_max_growth=last_month_max_growth,
|
|
324
|
-
last_year_max_growth=last_year_max_growth,
|
|
325
|
-
star_yoy=star_yoy,
|
|
326
|
-
star_mom=star_mom,
|
|
327
|
-
star_wow=star_wow,
|
|
328
|
-
)
|
|
121
|
+
res = Indicators().to_dict(prices)
|
|
122
|
+
return cls(last_price=prices.close.iloc[-1], **res)
|
|
329
123
|
except Exception as e:
|
|
330
124
|
logger.error(f"Failing to calculate technical analysis: {e}", exc_info=True)
|
|
331
|
-
return cls()
|
|
125
|
+
return cls()
|
|
332
126
|
|
|
333
127
|
|
|
334
128
|
class BaseFundamentalAnalysis(BaseModel):
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
129
|
+
positive_debt_to_equity: Optional[bool] = Field(
|
|
130
|
+
None,
|
|
131
|
+
description="True if the company's debt-to-equity ratio is favorable (typically low or improving).",
|
|
132
|
+
)
|
|
133
|
+
positive_return_on_assets: Optional[bool] = Field(
|
|
134
|
+
None,
|
|
135
|
+
description="True if the company reports a positive return on assets (ROA), "
|
|
136
|
+
"indicating efficient use of its assets.",
|
|
137
|
+
)
|
|
138
|
+
positive_return_on_equity: Optional[bool] = Field(
|
|
139
|
+
None,
|
|
140
|
+
description="True if the return on equity (ROE) is positive, "
|
|
141
|
+
"showing profitability relative to shareholder equity.",
|
|
142
|
+
)
|
|
143
|
+
positive_diluted_eps: Optional[bool] = Field(
|
|
144
|
+
None,
|
|
145
|
+
description="True if the diluted earnings per share (EPS), "
|
|
146
|
+
"which includes the effect of convertible securities, is positive.",
|
|
147
|
+
)
|
|
148
|
+
positive_basic_eps: Optional[bool] = Field(
|
|
149
|
+
None,
|
|
150
|
+
description="True if the basic earnings per share (EPS) is positive, reflecting profitable operations.",
|
|
151
|
+
)
|
|
152
|
+
growing_basic_eps: Optional[bool] = Field(
|
|
153
|
+
None,
|
|
154
|
+
description="True if the basic EPS has shown consistent growth over a defined time period.",
|
|
155
|
+
)
|
|
156
|
+
growing_diluted_eps: Optional[bool] = Field(
|
|
157
|
+
None,
|
|
158
|
+
description="True if the diluted EPS has consistently increased over time.",
|
|
159
|
+
)
|
|
160
|
+
positive_net_income: Optional[bool] = Field(
|
|
161
|
+
None,
|
|
162
|
+
description="True if the net income is positive, indicating overall profitability.",
|
|
163
|
+
)
|
|
164
|
+
positive_operating_income: Optional[bool] = Field(
|
|
165
|
+
None,
|
|
166
|
+
description="True if the company has positive operating income from its core business operations.",
|
|
167
|
+
)
|
|
168
|
+
growing_net_income: Optional[bool] = Field(
|
|
169
|
+
None, description="True if net income has shown consistent growth over time."
|
|
170
|
+
)
|
|
171
|
+
growing_operating_income: Optional[bool] = Field(
|
|
172
|
+
None,
|
|
173
|
+
description="True if the operating income has consistently increased over a period.",
|
|
174
|
+
)
|
|
175
|
+
positive_free_cash_flow: Optional[bool] = Field(
|
|
176
|
+
None,
|
|
177
|
+
description="True if the company has positive free cash flow, indicating financial flexibility and health.",
|
|
178
|
+
)
|
|
179
|
+
growing_operating_cash_flow: Optional[bool] = Field(
|
|
180
|
+
None,
|
|
181
|
+
description="True if the company's operating cash flow is growing steadily.",
|
|
182
|
+
)
|
|
183
|
+
operating_cash_flow_is_higher_than_net_income: Optional[bool] = Field(
|
|
184
|
+
None,
|
|
185
|
+
description="True if the operating cash flow exceeds net income, often a sign of high-quality earnings.",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Capital Expenditure Ratios
|
|
189
|
+
mean_capex_ratio: Optional[float] = Field(
|
|
190
|
+
None,
|
|
191
|
+
description="Average capital expenditure (CapEx) ratio, usually "
|
|
192
|
+
"calculated as CapEx divided by revenue or operating cash flow.",
|
|
193
|
+
)
|
|
194
|
+
max_capex_ratio: Optional[float] = Field(
|
|
195
|
+
None, description="Maximum observed CapEx ratio over the evaluation period."
|
|
196
|
+
)
|
|
197
|
+
min_capex_ratio: Optional[float] = Field(
|
|
198
|
+
None, description="Minimum observed CapEx ratio over the evaluation period."
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Dividend Payout Ratios
|
|
202
|
+
mean_dividend_payout_ratio: Optional[float] = Field(
|
|
203
|
+
None,
|
|
204
|
+
description="Average dividend payout ratio, representing the proportion of earnings paid out as dividends.",
|
|
205
|
+
)
|
|
206
|
+
max_dividend_payout_ratio: Optional[float] = Field(
|
|
207
|
+
None, description="Maximum dividend payout ratio observed over the period."
|
|
208
|
+
)
|
|
209
|
+
min_dividend_payout_ratio: Optional[float] = Field(
|
|
210
|
+
None, description="Minimum dividend payout ratio observed over the period."
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# EPS Value
|
|
214
|
+
earning_per_share: Optional[float] = Field(
|
|
215
|
+
None,
|
|
216
|
+
description="The latest or most relevant value of earnings per share (EPS), indicating net income per share.",
|
|
217
|
+
)
|
|
356
218
|
|
|
357
219
|
def is_empty(self) -> bool:
|
|
358
220
|
return all(getattr(self, field) is None for field in self.model_fields)
|
|
@@ -481,13 +343,15 @@ class BaseFundamentalAnalysis(BaseModel):
|
|
|
481
343
|
return cls()
|
|
482
344
|
|
|
483
345
|
|
|
484
|
-
class YearlyFundamentalAnalysis(BaseFundamentalAnalysis):
|
|
485
|
-
...
|
|
346
|
+
class YearlyFundamentalAnalysis(BaseFundamentalAnalysis): ...
|
|
486
347
|
|
|
487
348
|
|
|
488
349
|
fields_with_prefix = {
|
|
489
|
-
f"{QUARTERLY}_{name}": (
|
|
490
|
-
|
|
350
|
+
f"{QUARTERLY}_{name}": (
|
|
351
|
+
field_info.annotation,
|
|
352
|
+
Field(default=None, description=field_info.description),
|
|
353
|
+
)
|
|
354
|
+
for name, field_info in BaseFundamentalAnalysis.model_fields.items()
|
|
491
355
|
}
|
|
492
356
|
|
|
493
357
|
# Create the new model
|
|
@@ -507,7 +371,7 @@ class QuarterlyFundamentalAnalysis(BaseQuarterlyFundamentalAnalysis): # type: i
|
|
|
507
371
|
cash_flows=financials.quarterly_cash_flows,
|
|
508
372
|
ticker=ticker,
|
|
509
373
|
)
|
|
510
|
-
return cls.model_validate({f"{QUARTERLY}_{k}": v for k, v in base_financial_analisys.model_dump().items()}) # type: ignore
|
|
374
|
+
return cls.model_validate({f"{QUARTERLY}_{k}": v for k, v in base_financial_analisys.model_dump().items()}) # type: ignore
|
|
511
375
|
|
|
512
376
|
|
|
513
377
|
class FundamentalAnalysis(YearlyFundamentalAnalysis, QuarterlyFundamentalAnalysis):
|