bullishpy 0.17.0__tar.gz → 0.18.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 (56) hide show
  1. {bullishpy-0.17.0 → bullishpy-0.18.0}/PKG-INFO +1 -1
  2. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/analysis.py +18 -1
  3. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/constants.py +2 -0
  4. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/industry_views.py +23 -4
  5. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/predefined_filters.py +17 -2
  6. bullishpy-0.18.0/bullish/database/alembic/versions/79bc71ec6f9e_.py +39 -0
  7. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/crud.py +5 -4
  8. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/figures/figures.py +15 -28
  9. {bullishpy-0.17.0 → bullishpy-0.18.0}/pyproject.toml +1 -1
  10. {bullishpy-0.17.0 → bullishpy-0.18.0}/README.md +0 -0
  11. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/__init__.py +0 -0
  12. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/__init__.py +0 -0
  13. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/backtest.py +0 -0
  14. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/filter.py +0 -0
  15. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/functions.py +0 -0
  16. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/analysis/indicators.py +0 -0
  17. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/app/__init__.py +0 -0
  18. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/app/app.py +0 -0
  19. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/cli.py +0 -0
  20. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/__init__.py +0 -0
  21. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/README +0 -0
  22. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/alembic.ini +0 -0
  23. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/env.py +0 -0
  24. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/script.py.mako +0 -0
  25. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/037dbd721317_.py +0 -0
  26. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/040b15fba458_.py +0 -0
  27. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/08ac1116e055_.py +0 -0
  28. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/11d35a452b40_.py +0 -0
  29. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/12889a2cbd7d_.py +0 -0
  30. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/17e51420e7ad_.py +0 -0
  31. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/49c83f9eb5ac_.py +0 -0
  32. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/4b0a2f40b7d3_.py +0 -0
  33. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/5b10ee7604c1_.py +0 -0
  34. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/6d252e23f543_.py +0 -0
  35. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/73564b60fe24_.py +0 -0
  36. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/b76079e9845f_.py +0 -0
  37. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/bf6b86dd5463_.py +0 -0
  38. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/d663166c531d_.py +0 -0
  39. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/ec25c8fa449f_.py +0 -0
  40. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/ee5baabb35f8_.py +0 -0
  41. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/fc191121f522_.py +0 -0
  42. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/schemas.py +0 -0
  43. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/scripts/create_revision.py +0 -0
  44. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/scripts/stamp.py +0 -0
  45. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/scripts/upgrade.py +0 -0
  46. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/database/settings.py +0 -0
  47. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/exceptions.py +0 -0
  48. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/figures/__init__.py +0 -0
  49. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/interface/__init__.py +0 -0
  50. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/interface/interface.py +0 -0
  51. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/jobs/__init__.py +0 -0
  52. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/jobs/app.py +0 -0
  53. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/jobs/models.py +0 -0
  54. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/jobs/tasks.py +0 -0
  55. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/utils/__init__.py +0 -0
  56. {bullishpy-0.17.0 → bullishpy-0.18.0}/bullish/utils/checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bullishpy
3
- Version: 0.17.0
3
+ Version: 0.18.0
4
4
  Summary:
5
5
  Author: aan
6
6
  Author-email: andoludovic.andriamamonjy@gmail.com
@@ -114,6 +114,13 @@ class TechnicalAnalysisBase(BaseModel):
114
114
  default=None,
115
115
  ),
116
116
  ]
117
+ max_year_loss: Annotated[
118
+ Optional[float],
119
+ BeforeValidator(to_float),
120
+ Field(
121
+ default=None,
122
+ ),
123
+ ]
117
124
 
118
125
 
119
126
  TechnicalAnalysisModels = [*IndicatorModels, TechnicalAnalysisBase]
@@ -131,7 +138,10 @@ class TechnicalAnalysis(*TechnicalAnalysisModels): # type: ignore
131
138
  return cls()
132
139
  try:
133
140
  res = Indicators().compute(prices)
134
- return cls(last_price=prices.close.iloc[-1], **res)
141
+ last_price = prices.close.iloc[-1]
142
+ max_price = prices.close.iloc[-253 * 2 :].max()
143
+ max_year_loss = (max_price - last_price) / max_price
144
+ return cls(last_price=last_price, max_year_loss=max_year_loss, **res)
135
145
  except Exception as e:
136
146
  logger.error(
137
147
  f"Failing to calculate technical analysis for {ticker.symbol}: {e}",
@@ -467,6 +477,13 @@ class AnalysisView(BaseModel):
467
477
  default=None,
468
478
  ),
469
479
  ]
480
+ max_year_loss: Annotated[
481
+ Optional[float],
482
+ BeforeValidator(to_float),
483
+ Field(
484
+ default=None,
485
+ ),
486
+ ]
470
487
  median_yearly_growth: Optional[float] = None
471
488
  median_weekly_growth: Optional[float] = None
472
489
  median_monthly_growth: Optional[float] = None
@@ -308,6 +308,8 @@ Sector = Literal[
308
308
  "Financial Services",
309
309
  "Conglomerates",
310
310
  ]
311
+
312
+ SubCountry = Literal["United kingdom", "United states", "Germany", "Belgium", "France"]
311
313
  Country = Literal[
312
314
  "Australia",
313
315
  "China",
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import logging
2
3
  from typing import (
3
4
  Optional,
4
5
  Any,
@@ -17,11 +18,18 @@ from bearish.models.price.prices import Prices # type: ignore
17
18
  from bearish.models.query.query import AssetQuery, Symbols # type: ignore
18
19
  from pydantic import BaseModel, BeforeValidator, Field, model_validator
19
20
 
20
- from bullish.analysis.constants import Industry, IndustryGroup, Sector, Country
21
+ from bullish.analysis.constants import (
22
+ Industry,
23
+ IndustryGroup,
24
+ Sector,
25
+ Country,
26
+ SubCountry,
27
+ )
21
28
 
22
29
  if TYPE_CHECKING:
23
30
  from bullish.database.crud import BullishDb
24
31
 
32
+ logger = logging.getLogger(__name__)
25
33
  Type = Literal["Mean"]
26
34
 
27
35
  FUNCTIONS = {"Mean": np.mean}
@@ -47,12 +55,23 @@ def get_industry_comparison_data(
47
55
  country: Country,
48
56
  ) -> pd.DataFrame:
49
57
  try:
58
+
50
59
  views = bullish_db.read_returns(type, industry, country)
51
60
  industry_data = IndustryViews.from_views(views).to_dataframe()
52
61
  normalized_symbol = compute_normalized_close(symbol_data.close).rename("symbol")
53
- normalized_industry = industry_data.normalized_close.rename("industry")
54
- return pd.concat([normalized_symbol, normalized_industry], axis=1)
55
- except Exception:
62
+ normalized_industry = industry_data.normalized_close.rename(industry)
63
+ data = [normalized_symbol, normalized_industry]
64
+ for country in get_args(SubCountry):
65
+ views = bullish_db.read_returns(type, industry, country)
66
+ if views:
67
+ industry_data = IndustryViews.from_views(views).to_dataframe()
68
+ normalized_industry = industry_data.normalized_close.rename(
69
+ f"{industry}-{country}"
70
+ )
71
+ data.append(normalized_industry)
72
+ return pd.concat(data, axis=1)
73
+ except Exception as e:
74
+ logger.error(e)
56
75
  return pd.DataFrame()
57
76
 
58
77
 
@@ -176,7 +176,7 @@ RSI_CROSSOVER_30_GROWTH_STOCK = NamedFilterQuery(
176
176
  name="RSI cross-over 30 growth stock",
177
177
  price_per_earning_ratio=[10, 500],
178
178
  rsi_bullish_crossover_30=DATE_THRESHOLD,
179
- market_capitalization=[5e8, 1e12],
179
+ market_capitalization=[1e10, 1e13],
180
180
  order_by_desc="market_capitalization",
181
181
  country=[
182
182
  "Germany",
@@ -188,11 +188,25 @@ RSI_CROSSOVER_30_GROWTH_STOCK = NamedFilterQuery(
188
188
  "Belgium",
189
189
  ],
190
190
  )
191
+
192
+ MEDIAN_YEARLY_GROWTH = NamedFilterQuery(
193
+ name="Median yearly growth",
194
+ market_capitalization=[1e6, 1e13],
195
+ median_yearly_growth=[40, 1000],
196
+ last_price=[1, 100],
197
+ order_by_asc="last_price",
198
+ country=[
199
+ "Germany",
200
+ "United states",
201
+ "France",
202
+ "Belgium",
203
+ ],
204
+ )
191
205
  RSI_CROSSOVER_40_GROWTH_STOCK = NamedFilterQuery(
192
206
  name="RSI cross-over 40 growth stock",
193
207
  price_per_earning_ratio=[10, 500],
194
208
  rsi_bullish_crossover_40=DATE_THRESHOLD,
195
- market_capitalization=[5e8, 1e12],
209
+ market_capitalization=[1e10, 1e13],
196
210
  order_by_desc="market_capitalization",
197
211
  country=[
198
212
  "Germany",
@@ -360,6 +374,7 @@ def predefined_filters() -> list[NamedFilterQuery]:
360
374
  MOMENTUM_GROWTH_RSI_30,
361
375
  MOMENTUM_GROWTH_RSI_40,
362
376
  GOLDEN_CROSS_LAST_SEVEN_DAYS,
377
+ MEDIAN_YEARLY_GROWTH,
363
378
  ]
364
379
 
365
380
 
@@ -0,0 +1,39 @@
1
+ """
2
+
3
+ Revision ID: 79bc71ec6f9e
4
+ Revises: 6d252e23f543
5
+ Create Date: 2025-08-02 15:07:58.266933
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 = "79bc71ec6f9e"
17
+ down_revision: Union[str, None] = "6d252e23f543"
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
+
25
+ with op.batch_alter_table("analysis", schema=None) as batch_op:
26
+ batch_op.add_column(sa.Column("max_year_loss", sa.Float(), nullable=True))
27
+ batch_op.create_index(
28
+ "ix_analysis_max_year_loss", ["max_year_loss"], unique=False
29
+ )
30
+ # ### end Alembic commands ###
31
+
32
+
33
+ def downgrade() -> None:
34
+ # ### commands auto generated by Alembic - please adjust! ###
35
+ with op.batch_alter_table("analysis", schema=None) as batch_op:
36
+ batch_op.drop_index("ix_analysis_max_year_loss")
37
+ batch_op.drop_column("max_year_loss")
38
+
39
+ # ### end Alembic commands ###
@@ -261,10 +261,11 @@ class BullishDb(BearishDb, BullishDbBase): # type: ignore
261
261
  self, type: Type, industry: Industry, country: Country
262
262
  ) -> List[IndustryView]:
263
263
  with Session(self._engine) as session:
264
- stmt = select(IndustryViewORM).where(
265
- IndustryViewORM.industry == industry,
266
- IndustryViewORM.country == country,
267
- )
264
+ stmt = select(IndustryViewORM)
265
+ if industry:
266
+ stmt = stmt.where(IndustryViewORM.industry == industry)
267
+ if country:
268
+ stmt = stmt.where(IndustryViewORM.country == country)
268
269
  result = session.exec(stmt).all()
269
270
  return [IndustryView.model_validate(r) for r in result]
270
271
 
@@ -37,7 +37,7 @@ def plot(
37
37
  f"MACD ({symbol} [{name}])",
38
38
  f"ADX ({symbol} [{name}])",
39
39
  f"OBV ({symbol} [{name}])",
40
- f"ATR ({symbol} [{name}])",
40
+ f"Industry ({symbol} [{name}])",
41
41
  ),
42
42
  )
43
43
  # Row 1: Candlestick + SMAs
@@ -123,33 +123,20 @@ def plot(
123
123
  row=6,
124
124
  col=1,
125
125
  )
126
- if (
127
- industry_data is not None
128
- and not industry_data.empty
129
- and "symbol" in industry_data.columns
130
- and "industry" in industry_data.columns
131
- ):
132
- fig.add_trace(
133
- go.Scatter(
134
- x=industry_data.index,
135
- y=industry_data.symbol,
136
- name="Symbol",
137
- mode="lines",
138
- ),
139
- row=7,
140
- col=1,
141
- )
142
- fig.add_trace(
143
- go.Scatter(
144
- x=industry_data.index,
145
- y=industry_data.industry,
146
- name="Industry",
147
- mode="lines",
148
- opacity=0.5,
149
- ),
150
- row=7,
151
- col=1,
152
- )
126
+ if industry_data is not None and not industry_data.empty:
127
+ for c in industry_data.columns:
128
+ fig.add_trace(
129
+ go.Scatter(
130
+ x=industry_data.index,
131
+ y=industry_data[c],
132
+ name=c,
133
+ mode="lines",
134
+ opacity=0.5 if c != "symbol" else 1.0,
135
+ ),
136
+ row=7,
137
+ col=1,
138
+ )
139
+
153
140
  if dates is not None and dates:
154
141
  for date in dates:
155
142
  if (
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "bullishpy"
3
- version = "0.17.0"
3
+ version = "0.18.0"
4
4
  description = ""
5
5
  authors = ["aan <andoludovic.andriamamonjy@gmail.com>"]
6
6
  readme = "README.md"
File without changes
File without changes