bullishpy 0.62.0__py3-none-any.whl → 0.64.0__py3-none-any.whl
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.
- bullish/analysis/filter.py +1 -0
- bullish/analysis/indicators.py +2 -2
- bullish/analysis/predefined_filters.py +93 -91
- {bullishpy-0.62.0.dist-info → bullishpy-0.64.0.dist-info}/METADATA +2 -2
- {bullishpy-0.62.0.dist-info → bullishpy-0.64.0.dist-info}/RECORD +8 -8
- {bullishpy-0.62.0.dist-info → bullishpy-0.64.0.dist-info}/LICENSE +0 -0
- {bullishpy-0.62.0.dist-info → bullishpy-0.64.0.dist-info}/WHEEL +0 -0
- {bullishpy-0.62.0.dist-info → bullishpy-0.64.0.dist-info}/entry_points.txt +0 -0
bullish/analysis/filter.py
CHANGED
|
@@ -178,6 +178,7 @@ class GeneralFilter(BaseModel):
|
|
|
178
178
|
)
|
|
179
179
|
market_capitalization: Optional[List[float]] = Field(default=[5e8, 1e12])
|
|
180
180
|
price_per_earning_ratio: Optional[List[float]] = Field(default=[0.0, 1000.0])
|
|
181
|
+
last_price: Optional[List[float]] = Field(default=[0.0, 100000.0])
|
|
181
182
|
|
|
182
183
|
|
|
183
184
|
class FilterQuery(GeneralFilter, *TechnicalAnalysisFilters, *FundamentalAnalysisFilters): # type: ignore
|
bullish/analysis/indicators.py
CHANGED
|
@@ -266,7 +266,7 @@ def indicators_factory() -> List[Indicator]:
|
|
|
266
266
|
description="RSI Oversold Signal",
|
|
267
267
|
type_info="Oversold",
|
|
268
268
|
type=Optional[date],
|
|
269
|
-
function=lambda d: (d.RSI
|
|
269
|
+
function=lambda d: (d.RSI <= 30) & (d.RSI > 0),
|
|
270
270
|
in_use_backtest=True,
|
|
271
271
|
),
|
|
272
272
|
Signal(
|
|
@@ -281,7 +281,7 @@ def indicators_factory() -> List[Indicator]:
|
|
|
281
281
|
description="RSI Neutral Signal",
|
|
282
282
|
type_info="Overbought",
|
|
283
283
|
type=Optional[date],
|
|
284
|
-
function=lambda d: (d.RSI < 60) & (d.RSI >
|
|
284
|
+
function=lambda d: (d.RSI < 60) & (d.RSI > 30),
|
|
285
285
|
),
|
|
286
286
|
],
|
|
287
287
|
),
|
|
@@ -3,7 +3,7 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
from datetime import timedelta
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Dict, Any, Optional, List, Union, get_args
|
|
6
|
+
from typing import Dict, Any, Optional, List, Union, get_args, Tuple
|
|
7
7
|
|
|
8
8
|
from bullish.analysis.analysis import AnalysisView
|
|
9
9
|
from bullish.analysis.backtest import (
|
|
@@ -30,6 +30,10 @@ DATE_THRESHOLD = [
|
|
|
30
30
|
]
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _get_variants(variants: List[str]) -> List[Tuple[str, ...]]:
|
|
34
|
+
return [tuple(variants[:i]) for i in range(1, len(variants) + 1)]
|
|
35
|
+
|
|
36
|
+
|
|
33
37
|
class NamedFilterQuery(FilterQuery):
|
|
34
38
|
name: str
|
|
35
39
|
description: Optional[str] = None
|
|
@@ -157,57 +161,29 @@ class NamedFilterQuery(FilterQuery):
|
|
|
157
161
|
}
|
|
158
162
|
return self._custom_variant("Poor Performers", properties)
|
|
159
163
|
|
|
160
|
-
def
|
|
164
|
+
def fundamentals(self) -> "NamedFilterQuery":
|
|
161
165
|
properties = {
|
|
162
166
|
"income": [
|
|
163
167
|
"positive_operating_income",
|
|
164
168
|
"positive_net_income",
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
],
|
|
168
|
-
"cash_flow": [
|
|
169
|
-
"positive_free_cash_flow",
|
|
170
|
-
"quarterly_positive_free_cash_flow",
|
|
169
|
+
"growing_net_income",
|
|
170
|
+
"growing_operating_income",
|
|
171
171
|
],
|
|
172
|
+
"cash_flow": ["positive_free_cash_flow", "growing_operating_cash_flow"],
|
|
172
173
|
"eps": [
|
|
174
|
+
"growing_basic_eps",
|
|
175
|
+
"growing_diluted_eps",
|
|
173
176
|
"positive_basic_eps",
|
|
174
177
|
"positive_diluted_eps",
|
|
175
|
-
"quarterly_positive_basic_eps",
|
|
176
|
-
"quarterly_positive_diluted_eps",
|
|
177
178
|
],
|
|
178
179
|
"properties": [
|
|
179
180
|
"positive_return_on_assets",
|
|
180
181
|
"positive_return_on_equity",
|
|
181
182
|
"positive_debt_to_equity",
|
|
182
183
|
"operating_cash_flow_is_higher_than_net_income",
|
|
183
|
-
"quarterly_positive_return_on_assets",
|
|
184
|
-
"quarterly_positive_return_on_equity",
|
|
185
|
-
"quarterly_positive_debt_to_equity",
|
|
186
|
-
"quarterly_operating_cash_flow_is_higher_than_net_income",
|
|
187
184
|
],
|
|
188
185
|
}
|
|
189
|
-
return self._custom_variant("
|
|
190
|
-
|
|
191
|
-
def long_term_profitability(self) -> "NamedFilterQuery":
|
|
192
|
-
properties = {
|
|
193
|
-
"income": [
|
|
194
|
-
"growing_net_income",
|
|
195
|
-
"growing_operating_income",
|
|
196
|
-
"quarterly_growing_net_income",
|
|
197
|
-
"quarterly_growing_operating_income",
|
|
198
|
-
],
|
|
199
|
-
"cash_flow": [
|
|
200
|
-
"growing_operating_cash_flow",
|
|
201
|
-
"quarterly_growing_operating_cash_flow",
|
|
202
|
-
],
|
|
203
|
-
"eps": [
|
|
204
|
-
"growing_basic_eps",
|
|
205
|
-
"growing_diluted_eps",
|
|
206
|
-
"quarterly_growing_basic_eps",
|
|
207
|
-
"quarterly_growing_diluted_eps",
|
|
208
|
-
],
|
|
209
|
-
}
|
|
210
|
-
return self._custom_variant("Long-term profitability", properties)
|
|
186
|
+
return self._custom_variant("Fundamentals", properties)
|
|
211
187
|
|
|
212
188
|
def high_growth(self) -> "NamedFilterQuery":
|
|
213
189
|
properties = {"industry": list(get_args(HighGrowthIndustry))}
|
|
@@ -217,48 +193,57 @@ class NamedFilterQuery(FilterQuery):
|
|
|
217
193
|
properties = {"industry": list(get_args(DefensiveIndustries))}
|
|
218
194
|
return self._custom_variant("Defensive", properties)
|
|
219
195
|
|
|
220
|
-
def
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
196
|
+
def cheap(self) -> "NamedFilterQuery":
|
|
197
|
+
properties = {"last_price": [1, 30]}
|
|
198
|
+
return self._custom_variant("Cheap", properties)
|
|
199
|
+
|
|
200
|
+
def europe(self) -> "NamedFilterQuery":
|
|
201
|
+
return self.country_variant("Europe", list(get_args(Europe)))
|
|
202
|
+
|
|
203
|
+
def us(self) -> "NamedFilterQuery":
|
|
204
|
+
return self.country_variant("Us", list(get_args(Us)))
|
|
205
|
+
|
|
206
|
+
def rsi_30(self) -> "NamedFilterQuery":
|
|
207
|
+
return self.update_indicator_filter("RSI 30", "rsi_bullish_crossover_30")
|
|
208
|
+
|
|
209
|
+
def rsi_40(self) -> "NamedFilterQuery":
|
|
210
|
+
return self.update_indicator_filter("RSI 40", "rsi_bullish_crossover_40")
|
|
211
|
+
|
|
212
|
+
def macd(self) -> "NamedFilterQuery":
|
|
213
|
+
return self.update_indicator_filter("MACD", "macd_12_26_9_bullish_crossover")
|
|
214
|
+
|
|
215
|
+
def rsi_neutral_(self) -> "NamedFilterQuery":
|
|
216
|
+
return self.update_indicator_filter("RSI Neutral", "rsi_neutral")
|
|
217
|
+
|
|
218
|
+
def rsi_oversold_(self) -> "NamedFilterQuery":
|
|
219
|
+
return self.update_indicator_filter("RSI Oversold", "rsi_oversold")
|
|
220
|
+
|
|
221
|
+
def earnings_date(self) -> "NamedFilterQuery":
|
|
222
|
+
return NamedFilterQuery.model_validate(
|
|
223
|
+
self.model_dump()
|
|
224
|
+
| {
|
|
225
|
+
"name": f"{self.name} (Earnings Date)",
|
|
226
|
+
"next_earnings_date": [
|
|
227
|
+
datetime.date.today(),
|
|
228
|
+
datetime.date.today() + timedelta(days=20),
|
|
229
|
+
],
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def variants(
|
|
234
|
+
self, variants: Optional[List[List[str]]] = None
|
|
235
|
+
) -> List["NamedFilterQuery"]:
|
|
236
|
+
variants = variants or [["europe"], ["us"]]
|
|
237
|
+
|
|
238
|
+
_variants = {v for variant in variants for v in _get_variants(variant)}
|
|
239
|
+
filters = []
|
|
240
|
+
for attributes in _variants:
|
|
241
|
+
filter = self
|
|
242
|
+
for attr in attributes:
|
|
243
|
+
filter = getattr(filter, attr)()
|
|
244
|
+
filters.append(filter)
|
|
245
|
+
|
|
246
|
+
return filters
|
|
262
247
|
|
|
263
248
|
|
|
264
249
|
def load_custom_filters() -> List[NamedFilterQuery]:
|
|
@@ -282,28 +267,46 @@ SMALL_CAP = NamedFilterQuery(
|
|
|
282
267
|
properties=["positive_debt_to_equity"],
|
|
283
268
|
average_volume_30=[50000, 5e9],
|
|
284
269
|
order_by_desc="market_capitalization",
|
|
285
|
-
).variants(
|
|
270
|
+
).variants(
|
|
271
|
+
variants=[
|
|
272
|
+
["europe", "top_performers", "fundamentals"],
|
|
273
|
+
["us", "top_performers", "fundamentals"],
|
|
274
|
+
["europe", "earnings_date"],
|
|
275
|
+
["us", "earnings_date"],
|
|
276
|
+
]
|
|
277
|
+
)
|
|
286
278
|
|
|
287
279
|
LARGE_CAPS = NamedFilterQuery(
|
|
288
280
|
name="Large Cap",
|
|
289
281
|
order_by_desc="market_capitalization",
|
|
290
282
|
market_capitalization=[1e10, 1e14],
|
|
291
|
-
).variants(
|
|
283
|
+
).variants(
|
|
284
|
+
variants=[
|
|
285
|
+
["europe", "rsi_oversold_", "macd", "fundamentals"],
|
|
286
|
+
["us", "rsi_oversold_", "macd", "fundamentals"],
|
|
287
|
+
["europe", "rsi_neutral_", "macd", "fundamentals"],
|
|
288
|
+
["us", "rsi_neutral_", "macd", "fundamentals"],
|
|
289
|
+
["europe", "rsi_30", "macd", "fundamentals"],
|
|
290
|
+
["us", "rsi_30", "macd", "fundamentals"],
|
|
291
|
+
["europe", "top_performers", "cheap"],
|
|
292
|
+
["us", "top_performers", "cheap"],
|
|
293
|
+
["europe", "earnings_date"],
|
|
294
|
+
["us", "earnings_date"],
|
|
295
|
+
]
|
|
296
|
+
)
|
|
292
297
|
|
|
293
298
|
MID_CAPS = NamedFilterQuery(
|
|
294
299
|
name="Mid Cap",
|
|
295
300
|
order_by_desc="market_capitalization",
|
|
296
301
|
market_capitalization=[5e8, 1e10],
|
|
297
|
-
).variants(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
],
|
|
306
|
-
).variants()
|
|
302
|
+
).variants(
|
|
303
|
+
variants=[
|
|
304
|
+
["europe", "top_performers", "fundamentals"],
|
|
305
|
+
["us", "top_performers", "fundamentals"],
|
|
306
|
+
["europe", "earnings_date"],
|
|
307
|
+
["us", "earnings_date"],
|
|
308
|
+
]
|
|
309
|
+
)
|
|
307
310
|
|
|
308
311
|
|
|
309
312
|
def predefined_filters() -> list[NamedFilterQuery]:
|
|
@@ -312,7 +315,6 @@ def predefined_filters() -> list[NamedFilterQuery]:
|
|
|
312
315
|
*SMALL_CAP,
|
|
313
316
|
*MID_CAPS,
|
|
314
317
|
*LARGE_CAPS,
|
|
315
|
-
*NEXT_EARNINGS_DATE,
|
|
316
318
|
]
|
|
317
319
|
|
|
318
320
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: bullishpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.64.0
|
|
4
4
|
Summary:
|
|
5
5
|
Author: aan
|
|
6
6
|
Author-email: andoludovic.andriamamonjy@gmail.com
|
|
@@ -17,7 +17,7 @@ Requires-Dist: streamlit (>=1.45.1,<2.0.0)
|
|
|
17
17
|
Requires-Dist: streamlit-file-browser (>=3.2.22,<4.0.0)
|
|
18
18
|
Requires-Dist: streamlit-pydantic (>=v0.6.1-rc.3,<0.7.0)
|
|
19
19
|
Requires-Dist: ta-lib (>=0.6.4,<0.7.0)
|
|
20
|
-
Requires-Dist: tickermood (>=0.
|
|
20
|
+
Requires-Dist: tickermood (>=0.28.0,<0.29.0)
|
|
21
21
|
Requires-Dist: vectorbt (>=0.28.0,<0.29.0)
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
|
|
@@ -3,11 +3,11 @@ bullish/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
3
3
|
bullish/analysis/analysis.py,sha256=Bcupt-qROPddj1hGTNAY8vhu0pnFqNvXoDtUNhRXErY,24217
|
|
4
4
|
bullish/analysis/backtest.py,sha256=x91ek5kOzJHvYq0TmJh1Q8wBDDduIaieE0zDaoZFXew,14325
|
|
5
5
|
bullish/analysis/constants.py,sha256=j3vQwjGhY-4dEEV-TkeKMDUTo2GM7M97Hcpi19LDcFQ,11458
|
|
6
|
-
bullish/analysis/filter.py,sha256=
|
|
6
|
+
bullish/analysis/filter.py,sha256=aE1wsjBp3df8WxAuZjKU-fcNhHGpj_jtpFXzLOJ2_kc,9397
|
|
7
7
|
bullish/analysis/functions.py,sha256=CuMgOjpQeg4KsDMUBdHRlxL1dRlos16KRyLhQe8PYUQ,14819
|
|
8
|
-
bullish/analysis/indicators.py,sha256=
|
|
8
|
+
bullish/analysis/indicators.py,sha256=Seig6LaY6qr8QhgWF_qKNR68GSer9HN0VZIkAMfdUXc,27649
|
|
9
9
|
bullish/analysis/industry_views.py,sha256=-B4CCAYz2arGQtWTXLLMpox0loO_MGdVQd2ycCRMOQQ,6799
|
|
10
|
-
bullish/analysis/predefined_filters.py,sha256=
|
|
10
|
+
bullish/analysis/predefined_filters.py,sha256=zIp1po5SOIwqfEbzmrdV6frfxUAb7RZ8o5HFyoyoGqQ,11512
|
|
11
11
|
bullish/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
bullish/app/app.py,sha256=7hWVVd2jBM-Es9S904ck1mtIMSadWgFqwns0bTwrKOU,16720
|
|
13
13
|
bullish/cli.py,sha256=yYqiEQAvOIQ-pTn77RPuE449gwaEGBeQwNHHAJ5yQDM,2739
|
|
@@ -56,8 +56,8 @@ bullish/jobs/models.py,sha256=rBXxtGFBpgZprrxq5_X2Df-bh8BLYEfw-VLMRucrqa8,784
|
|
|
56
56
|
bullish/jobs/tasks.py,sha256=7_zKZaLpbmh7XxvjhfWcowdDAp9sEABULB2PSkasfbM,6509
|
|
57
57
|
bullish/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
58
|
bullish/utils/checks.py,sha256=g-5QXNWNe1_BwHKrc2PtvPiLraL0tqGgxnzG7u-Wkgo,2189
|
|
59
|
-
bullishpy-0.
|
|
60
|
-
bullishpy-0.
|
|
61
|
-
bullishpy-0.
|
|
62
|
-
bullishpy-0.
|
|
63
|
-
bullishpy-0.
|
|
59
|
+
bullishpy-0.64.0.dist-info/LICENSE,sha256=nYb7AJFegu6ndlQhbbk54MjT-GH-0x9RF6Ls-ggJ_g4,1075
|
|
60
|
+
bullishpy-0.64.0.dist-info/METADATA,sha256=bGINvDvBm0oYFWas0lNuJbHikrPuWR-_knvzN_FXZ7A,3009
|
|
61
|
+
bullishpy-0.64.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
62
|
+
bullishpy-0.64.0.dist-info/entry_points.txt,sha256=eaPpmL6vmSBFo0FBtwibCXGqAW4LFJ83whJzT1VjD-0,43
|
|
63
|
+
bullishpy-0.64.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|