bullishpy 0.16.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.
- {bullishpy-0.16.0 → bullishpy-0.18.0}/PKG-INFO +1 -1
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/analysis.py +18 -1
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/constants.py +2 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/industry_views.py +23 -4
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/predefined_filters.py +17 -2
- bullishpy-0.18.0/bullish/database/alembic/versions/79bc71ec6f9e_.py +39 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/crud.py +9 -5
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/figures/figures.py +15 -28
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/jobs/tasks.py +2 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/pyproject.toml +1 -1
- {bullishpy-0.16.0 → bullishpy-0.18.0}/README.md +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/backtest.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/filter.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/functions.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/analysis/indicators.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/app/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/app/app.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/cli.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/README +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/alembic.ini +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/env.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/script.py.mako +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/037dbd721317_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/040b15fba458_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/08ac1116e055_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/11d35a452b40_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/12889a2cbd7d_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/17e51420e7ad_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/49c83f9eb5ac_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/4b0a2f40b7d3_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/5b10ee7604c1_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/6d252e23f543_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/73564b60fe24_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/b76079e9845f_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/bf6b86dd5463_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/d663166c531d_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/ec25c8fa449f_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/ee5baabb35f8_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/alembic/versions/fc191121f522_.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/schemas.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/scripts/create_revision.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/scripts/stamp.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/scripts/upgrade.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/database/settings.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/exceptions.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/figures/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/interface/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/interface/interface.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/jobs/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/jobs/app.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/jobs/models.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/utils/__init__.py +0 -0
- {bullishpy-0.16.0 → bullishpy-0.18.0}/bullish/utils/checks.py +0 -0
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
|
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(
|
|
54
|
-
|
|
55
|
-
|
|
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=[
|
|
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=[
|
|
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)
|
|
265
|
-
|
|
266
|
-
IndustryViewORM.
|
|
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
|
|
|
@@ -326,4 +327,7 @@ class BullishDb(BearishDb, BullishDbBase): # type: ignore
|
|
|
326
327
|
with Session(self._engine) as session:
|
|
327
328
|
stmt = select(BacktestResultORM)
|
|
328
329
|
results = session.exec(stmt).all()
|
|
329
|
-
|
|
330
|
+
if results:
|
|
331
|
+
return [BacktestResult.model_validate(r) for r in results]
|
|
332
|
+
else:
|
|
333
|
+
return []
|
|
@@ -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"
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
)
|
|
139
|
-
|
|
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 (
|
|
@@ -13,6 +13,7 @@ from huey.api import Task # type: ignore
|
|
|
13
13
|
from .models import JobTrackerStatus, JobTracker, JobType
|
|
14
14
|
from ..analysis.analysis import run_analysis, run_signal_series_analysis
|
|
15
15
|
from ..analysis.backtest import run_many_tests, BackTestConfig
|
|
16
|
+
from ..analysis.industry_views import compute_industry_view
|
|
16
17
|
from ..analysis.predefined_filters import predefined_filters
|
|
17
18
|
from ..database.crud import BullishDb
|
|
18
19
|
from bullish.analysis.filter import FilterUpdate
|
|
@@ -85,6 +86,7 @@ def analysis(
|
|
|
85
86
|
) -> None:
|
|
86
87
|
bullish_db = BullishDb(database_path=database_path)
|
|
87
88
|
run_analysis(bullish_db)
|
|
89
|
+
compute_industry_view(bullish_db)
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
@huey.task(context=True) # type: ignore
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|