bullishpy 0.7.0__tar.gz → 0.8.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.

Files changed (43) hide show
  1. {bullishpy-0.7.0 → bullishpy-0.8.0}/PKG-INFO +1 -1
  2. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/analysis/functions.py +20 -0
  3. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/analysis/indicators.py +34 -0
  4. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/analysis/predefined_filters.py +65 -1
  5. bullishpy-0.8.0/bullish/database/alembic/versions/fc191121f522_.py +57 -0
  6. {bullishpy-0.7.0 → bullishpy-0.8.0}/pyproject.toml +1 -1
  7. {bullishpy-0.7.0 → bullishpy-0.8.0}/README.md +0 -0
  8. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/__init__.py +0 -0
  9. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/analysis/__init__.py +0 -0
  10. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/analysis/analysis.py +0 -0
  11. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/analysis/filter.py +0 -0
  12. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/app/__init__.py +0 -0
  13. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/app/app.py +0 -0
  14. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/cli.py +0 -0
  15. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/__init__.py +0 -0
  16. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/README +0 -0
  17. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/alembic.ini +0 -0
  18. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/env.py +0 -0
  19. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/script.py.mako +0 -0
  20. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/037dbd721317_.py +0 -0
  21. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/08ac1116e055_.py +0 -0
  22. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/11d35a452b40_.py +0 -0
  23. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/49c83f9eb5ac_.py +0 -0
  24. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/4b0a2f40b7d3_.py +0 -0
  25. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/73564b60fe24_.py +0 -0
  26. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/alembic/versions/ee5baabb35f8_.py +0 -0
  27. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/crud.py +0 -0
  28. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/schemas.py +0 -0
  29. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/scripts/create_revision.py +0 -0
  30. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/scripts/stamp.py +0 -0
  31. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/scripts/upgrade.py +0 -0
  32. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/database/settings.py +0 -0
  33. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/exceptions.py +0 -0
  34. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/figures/__init__.py +0 -0
  35. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/figures/figures.py +0 -0
  36. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/interface/__init__.py +0 -0
  37. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/interface/interface.py +0 -0
  38. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/jobs/__init__.py +0 -0
  39. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/jobs/app.py +0 -0
  40. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/jobs/models.py +0 -0
  41. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/jobs/tasks.py +0 -0
  42. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/utils/__init__.py +0 -0
  43. {bullishpy-0.7.0 → bullishpy-0.8.0}/bullish/utils/checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bullishpy
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary:
5
5
  Author: aan
6
6
  Author-email: andoludovic.andriamamonjy@gmail.com
@@ -244,6 +244,16 @@ def compute_patterns(data: pd.DataFrame) -> pd.DataFrame:
244
244
  return results
245
245
 
246
246
 
247
+ def compute_price(data: pd.DataFrame) -> pd.DataFrame:
248
+ results = pd.DataFrame(index=data.index)
249
+ results["200_DAY_HIGH"] = data.close.rolling(window=200).max()
250
+ results["200_DAY_LOW"] = data.close.rolling(window=200).min()
251
+ results["20_DAY_HIGH"] = data.close.rolling(window=20).max()
252
+ results["20_DAY_LOW"] = data.close.rolling(window=20).min()
253
+ results["LAST_PRICE"] = data.close
254
+ return results
255
+
256
+
247
257
  class IndicatorFunction(BaseModel):
248
258
  expected_columns: list[str]
249
259
  functions: list[Callable[[pd.DataFrame], pd.DataFrame]]
@@ -330,6 +340,16 @@ TRANGE = IndicatorFunction(
330
340
  expected_columns=["TRANGE"],
331
341
  functions=[compute_trange, compute_pandas_ta_trange],
332
342
  )
343
+ PRICE = IndicatorFunction(
344
+ expected_columns=[
345
+ "200_DAY_HIGH",
346
+ "200_DAY_LOW",
347
+ "20_DAY_HIGH",
348
+ "20_DAY_LOW",
349
+ "LAST_PRICE",
350
+ ],
351
+ functions=[compute_price],
352
+ )
333
353
 
334
354
 
335
355
  def add_indicators(data: pd.DataFrame) -> pd.DataFrame:
@@ -18,6 +18,7 @@ from bullish.analysis.functions import (
18
18
  CANDLESTOCK_PATTERNS,
19
19
  SMA,
20
20
  ADOSC,
21
+ PRICE,
21
22
  )
22
23
 
23
24
  logger = logging.getLogger(__name__)
@@ -186,6 +187,13 @@ def indicators_factory() -> List[Indicator]:
186
187
  type=Optional[date],
187
188
  function=lambda d: d[(d.RSI < 100) & (d.RSI > 70)].index[-1],
188
189
  ),
190
+ Signal(
191
+ name="RSI_NEUTRAL",
192
+ description="RSI Neutral Signal",
193
+ type_info="Overbought",
194
+ type=Optional[date],
195
+ function=lambda d: d[(d.RSI < 60) & (d.RSI > 40)].index[-1],
196
+ ),
189
197
  ],
190
198
  ),
191
199
  Indicator(
@@ -254,6 +262,32 @@ def indicators_factory() -> List[Indicator]:
254
262
  ),
255
263
  ],
256
264
  ),
265
+ Indicator(
266
+ name="PRICE",
267
+ description="Price based indicators",
268
+ expected_columns=PRICE.expected_columns,
269
+ function=PRICE.call,
270
+ signals=[
271
+ Signal(
272
+ name="LOWER_THAN_200_DAY_HIGH",
273
+ description="Current price is lower than the 200-day high",
274
+ type_info="Oversold",
275
+ type=Optional[date],
276
+ function=lambda d: d[0.6 * d["200_DAY_HIGH"] > d.LAST_PRICE].index[
277
+ -1
278
+ ],
279
+ ),
280
+ Signal(
281
+ name="LOWER_THAN_20_DAY_HIGH",
282
+ description="Current price is lower than the 20-day high",
283
+ type_info="Oversold",
284
+ type=Optional[date],
285
+ function=lambda d: d[0.6 * d["20_DAY_HIGH"] > d.LAST_PRICE].index[
286
+ -1
287
+ ],
288
+ ),
289
+ ],
290
+ ),
257
291
  Indicator(
258
292
  name="ROC",
259
293
  description="Rate Of Change",
@@ -69,9 +69,73 @@ MICRO_CAP_EVENT_SPECULATION = NamedFilterQuery(
69
69
  market_capitalization=[0, 5e8],
70
70
  )
71
71
 
72
+ MOMENTUM_BREAKOUT_HUNTER = NamedFilterQuery(
73
+ name="Momentum Breakout Hunter",
74
+ description="A confluence of medium-term (50/200 MA) and "
75
+ "shorter oscillators suggests fresh upside momentum with fuel left.",
76
+ income=[
77
+ "positive_operating_income",
78
+ "positive_net_income",
79
+ ],
80
+ cash_flow=["positive_free_cash_flow"],
81
+ golden_cross=[
82
+ datetime.date.today() - datetime.timedelta(days=5),
83
+ datetime.date.today(),
84
+ ],
85
+ adx_14_long=[
86
+ datetime.date.today() - datetime.timedelta(days=5),
87
+ datetime.date.today(),
88
+ ],
89
+ rate_of_change_30=[0, 100],
90
+ rsi_neutral=[
91
+ datetime.date.today() - datetime.timedelta(days=5),
92
+ datetime.date.today(),
93
+ ],
94
+ )
95
+
96
+ DEEP_VALUE_PLUS_CATALYST = NamedFilterQuery(
97
+ name="Deep-Value Plus Catalyst",
98
+ description="Seeks beaten-down names that just printed a bullish "
99
+ "candle and early accumulation signals—often the first leg of a bottom.",
100
+ income=[
101
+ "positive_operating_income",
102
+ "positive_net_income",
103
+ ],
104
+ lower_than_200_day_high=[
105
+ datetime.date.today() - datetime.timedelta(days=5),
106
+ datetime.date.today(),
107
+ ],
108
+ rate_of_change_30=[3, 100],
109
+ rsi_bullish_crossover=[
110
+ datetime.date.today() - datetime.timedelta(days=5),
111
+ datetime.date.today(),
112
+ ],
113
+ )
114
+ END_OF_TREND_REVERSAL = NamedFilterQuery(
115
+ name="End of trend reversal",
116
+ description="Layers long-term MA breach with momentum exhaustion and a "
117
+ "bullish candle—classic setup for mean-reversion traders.",
118
+ death_cross=[
119
+ datetime.date.today() - datetime.timedelta(days=5),
120
+ datetime.date.today(),
121
+ ],
122
+ rsi_oversold=[
123
+ datetime.date.today() - datetime.timedelta(days=5),
124
+ datetime.date.today(),
125
+ ],
126
+ candlesticks=["cdlmorningstart", "cdlabandonedbaby", "cdl3whitesoldiers"],
127
+ )
128
+
72
129
 
73
130
  def predefined_filters() -> list[NamedFilterQuery]:
74
- return [STRONG_FUNDAMENTALS, GOOD_FUNDAMENTALS, MICRO_CAP_EVENT_SPECULATION]
131
+ return [
132
+ STRONG_FUNDAMENTALS,
133
+ GOOD_FUNDAMENTALS,
134
+ MICRO_CAP_EVENT_SPECULATION,
135
+ MOMENTUM_BREAKOUT_HUNTER,
136
+ DEEP_VALUE_PLUS_CATALYST,
137
+ END_OF_TREND_REVERSAL,
138
+ ]
75
139
 
76
140
 
77
141
  class PredefinedFilters(BaseModel):
@@ -0,0 +1,57 @@
1
+ """
2
+
3
+ Revision ID: fc191121f522
4
+ Revises: 49c83f9eb5ac
5
+ Create Date: 2025-07-06 17:33:02.231374
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+ import sqlalchemy as sa
13
+ from sqlalchemy.dialects import sqlite
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "fc191121f522"
17
+ down_revision: Union[str, None] = "49c83f9eb5ac"
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ with op.batch_alter_table("analysis", schema=None) as batch_op:
25
+ batch_op.add_column(
26
+ sa.Column("lower_than_200_day_high", sa.Date(), nullable=True)
27
+ )
28
+ batch_op.add_column(
29
+ sa.Column("lower_than_20_day_high", sa.Date(), nullable=True)
30
+ )
31
+ batch_op.add_column(sa.Column("rsi_neutral", sa.Date(), nullable=True))
32
+ batch_op.create_index(
33
+ "ix_analysis_lower_than_200_day_high",
34
+ ["lower_than_200_day_high"],
35
+ unique=False,
36
+ )
37
+ batch_op.create_index(
38
+ "ix_analysis_lower_than_20_day_high",
39
+ ["lower_than_20_day_high"],
40
+ unique=False,
41
+ )
42
+ batch_op.create_index("ix_analysis_rsi_neutral", ["rsi_neutral"], unique=False)
43
+
44
+ # ### end Alembic commands ###
45
+
46
+
47
+ def downgrade() -> None:
48
+ # ### commands auto generated by Alembic - please adjust! ###
49
+ with op.batch_alter_table("analysis", schema=None) as batch_op:
50
+ batch_op.drop_index("ix_analysis_rsi_neutral")
51
+ batch_op.drop_index("ix_analysis_lower_than_20_day_high")
52
+ batch_op.drop_index("ix_analysis_lower_than_200_day_high")
53
+ batch_op.drop_column("rsi_neutral")
54
+ batch_op.drop_column("lower_than_20_day_high")
55
+ batch_op.drop_column("lower_than_200_day_high")
56
+
57
+ # ### end Alembic commands ###
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "bullishpy"
3
- version = "0.7.0"
3
+ version = "0.8.0"
4
4
  description = ""
5
5
  authors = ["aan <andoludovic.andriamamonjy@gmail.com>"]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes