bullishpy 0.68.0__py3-none-any.whl → 0.70.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.
- bullish/analysis/analysis.py +15 -2
- bullish/analysis/functions.py +4 -4
- bullish/analysis/indicators.py +7 -0
- bullish/analysis/openai.py +3 -1
- bullish/database/alembic/versions/65662e214031_.py +48 -0
- bullish/database/alembic/versions/660897c02c00_.py +36 -0
- bullish/database/crud.py +1 -0
- {bullishpy-0.68.0.dist-info → bullishpy-0.70.0.dist-info}/METADATA +5 -4
- {bullishpy-0.68.0.dist-info → bullishpy-0.70.0.dist-info}/RECORD +12 -10
- {bullishpy-0.68.0.dist-info → bullishpy-0.70.0.dist-info}/WHEEL +1 -1
- {bullishpy-0.68.0.dist-info → bullishpy-0.70.0.dist-info}/entry_points.txt +0 -0
- {bullishpy-0.68.0.dist-info → bullishpy-0.70.0.dist-info/licenses}/LICENSE +0 -0
bullish/analysis/analysis.py
CHANGED
|
@@ -497,6 +497,11 @@ class AnalysisView(BaseModel):
|
|
|
497
497
|
weekly_growth: Optional[float] = None
|
|
498
498
|
monthly_growth: Optional[float] = None
|
|
499
499
|
upside: Optional[float] = None
|
|
500
|
+
oai_high_price_target: Optional[float] = None
|
|
501
|
+
oai_low_price_target: Optional[float] = None
|
|
502
|
+
rsi: Optional[float] = None
|
|
503
|
+
oai_recommendation: Optional[str] = None
|
|
504
|
+
oai_moat: Optional[bool] = None
|
|
500
505
|
|
|
501
506
|
|
|
502
507
|
def json_loads(value: Any) -> Any:
|
|
@@ -527,6 +532,7 @@ class SubjectAnalysis(BaseModel):
|
|
|
527
532
|
] = None
|
|
528
533
|
summary: Annotated[Optional[Dict[str, Any]], BeforeValidator(json_loads)] = None
|
|
529
534
|
upside: Optional[float] = None
|
|
535
|
+
downside: Optional[float] = None
|
|
530
536
|
|
|
531
537
|
oai_high_price_target: Optional[float] = None
|
|
532
538
|
oai_low_price_target: Optional[float] = None
|
|
@@ -534,11 +540,18 @@ class SubjectAnalysis(BaseModel):
|
|
|
534
540
|
oai_recent_news: Optional[str] = None
|
|
535
541
|
oai_recommendation: Optional[str] = None
|
|
536
542
|
oai_explanation: Optional[str] = None
|
|
543
|
+
oai_moat: Optional[bool] = None
|
|
537
544
|
|
|
538
545
|
def compute_upside(self, last_price: float) -> None:
|
|
539
|
-
if self.
|
|
546
|
+
if self.oai_high_price_target is not None:
|
|
540
547
|
self.upside = (
|
|
541
|
-
(float(self.
|
|
548
|
+
(float(self.oai_high_price_target) - float(last_price))
|
|
549
|
+
* 100
|
|
550
|
+
/ float(last_price)
|
|
551
|
+
)
|
|
552
|
+
if self.oai_low_price_target is not None:
|
|
553
|
+
self.downside = (
|
|
554
|
+
(float(last_price) - float(self.oai_low_price_target))
|
|
542
555
|
* 100
|
|
543
556
|
/ float(last_price)
|
|
544
557
|
)
|
bullish/analysis/functions.py
CHANGED
|
@@ -4,7 +4,7 @@ from typing import Optional, Callable, cast
|
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
|
-
import pandas_ta as ta
|
|
7
|
+
import pandas_ta as ta
|
|
8
8
|
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
|
|
@@ -18,7 +18,7 @@ except Exception:
|
|
|
18
18
|
def cross_simple(
|
|
19
19
|
series_a: pd.Series, series_b: pd.Series, above: bool = True
|
|
20
20
|
) -> pd.Series:
|
|
21
|
-
crossing = ta.cross(
|
|
21
|
+
crossing = ta.cross(x=series_a, y=series_b, above=above) # type: ignore
|
|
22
22
|
return crossing # type: ignore
|
|
23
23
|
|
|
24
24
|
|
|
@@ -466,6 +466,6 @@ def bollinger_bands(
|
|
|
466
466
|
data: pd.DataFrame, window: int = 20, std_dev: float = 2.0
|
|
467
467
|
) -> pd.DataFrame:
|
|
468
468
|
bbands = ta.bbands(
|
|
469
|
-
data.close, timeperiod=window, nbdevup=std_dev, nbdevdn=std_dev, matype=0
|
|
469
|
+
data.close, timeperiod=window, nbdevup=std_dev, nbdevdn=std_dev, matype=0 # type: ignore
|
|
470
470
|
)
|
|
471
|
-
return bbands
|
|
471
|
+
return bbands
|
bullish/analysis/indicators.py
CHANGED
|
@@ -297,6 +297,13 @@ def indicators_factory() -> List[Indicator]:
|
|
|
297
297
|
type=Optional[date],
|
|
298
298
|
function=lambda d: (d.RSI < 60) & (d.RSI > 30),
|
|
299
299
|
),
|
|
300
|
+
Signal(
|
|
301
|
+
name="RSI",
|
|
302
|
+
description="RSI value",
|
|
303
|
+
type_info="Overbought",
|
|
304
|
+
type=Optional[float],
|
|
305
|
+
function=lambda d: d.RSI,
|
|
306
|
+
),
|
|
300
307
|
],
|
|
301
308
|
),
|
|
302
309
|
Indicator(
|
bullish/analysis/openai.py
CHANGED
|
@@ -31,6 +31,7 @@ fences, no extra text:
|
|
|
31
31
|
"recommendation": str, // One of: "Strong Buy", "Buy", "Hold", "Sell", "Strong Sell"
|
|
32
32
|
"explanation": str // Concise explanation for the recommendation above, covering key pros/cons
|
|
33
33
|
for investors
|
|
34
|
+
"moat": bool // Give as a boolean true or false if the company has a strong economic moat
|
|
34
35
|
}}
|
|
35
36
|
|
|
36
37
|
Formatting rules:
|
|
@@ -49,6 +50,7 @@ class OpenAINews(BaseModel):
|
|
|
49
50
|
recent_news: Optional[str] = None
|
|
50
51
|
recommendation: Optional[str] = None
|
|
51
52
|
explanation: Optional[str] = None
|
|
53
|
+
moat: Optional[bool] = None
|
|
52
54
|
|
|
53
55
|
def valid(self) -> bool:
|
|
54
56
|
return bool(
|
|
@@ -67,7 +69,7 @@ class OpenAINews(BaseModel):
|
|
|
67
69
|
print(f"Fetching OpenAI news for {ticker}...")
|
|
68
70
|
client = OpenAI()
|
|
69
71
|
resp = client.responses.create(
|
|
70
|
-
model="gpt-4o
|
|
72
|
+
model="gpt-4o", input=prompt(ticker), tools=[{"type": "web_search"}] # type: ignore
|
|
71
73
|
)
|
|
72
74
|
try:
|
|
73
75
|
return cls.model_validate(json.loads(resp.output_text) | {"symbol": ticker})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Revision ID: 65662e214031
|
|
4
|
+
Revises: 660897c02c00
|
|
5
|
+
Create Date: 2025-08-20 17:30:47.973725
|
|
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 = "65662e214031"
|
|
17
|
+
down_revision: Union[str, None] = "660897c02c00"
|
|
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("downside", sa.Float(), nullable=True))
|
|
27
|
+
batch_op.add_column(sa.Column("oai_moat", sa.Boolean(), nullable=True))
|
|
28
|
+
batch_op.create_index("ix_analysis_downside", ["downside"], unique=False)
|
|
29
|
+
batch_op.create_index("ix_analysis_oai_moat", ["oai_moat"], unique=False)
|
|
30
|
+
|
|
31
|
+
with op.batch_alter_table("openai", schema=None) as batch_op:
|
|
32
|
+
batch_op.add_column(sa.Column("moat", sa.Boolean(), nullable=True))
|
|
33
|
+
|
|
34
|
+
# ### end Alembic commands ###
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def downgrade() -> None:
|
|
38
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
39
|
+
with op.batch_alter_table("openai", schema=None) as batch_op:
|
|
40
|
+
batch_op.drop_column("moat")
|
|
41
|
+
|
|
42
|
+
with op.batch_alter_table("analysis", schema=None) as batch_op:
|
|
43
|
+
batch_op.drop_index("ix_analysis_oai_moat")
|
|
44
|
+
batch_op.drop_index("ix_analysis_downside")
|
|
45
|
+
batch_op.drop_column("oai_moat")
|
|
46
|
+
batch_op.drop_column("downside")
|
|
47
|
+
|
|
48
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
Revision ID: 660897c02c00
|
|
4
|
+
Revises: c828e29e1105
|
|
5
|
+
Create Date: 2025-08-20 17:19:05.423318
|
|
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 = "660897c02c00"
|
|
17
|
+
down_revision: Union[str, None] = "c828e29e1105"
|
|
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("rsi", sa.Float(), nullable=True))
|
|
27
|
+
batch_op.create_index("ix_analysis_rsi", ["rsi"], unique=False)
|
|
28
|
+
|
|
29
|
+
# ### end Alembic commands ###
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def downgrade() -> None:
|
|
33
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
34
|
+
with op.batch_alter_table("analysis", schema=None) as batch_op:
|
|
35
|
+
batch_op.drop_index("ix_analysis_rsi")
|
|
36
|
+
batch_op.drop_column("rsi")
|
bullish/database/crud.py
CHANGED
|
@@ -389,6 +389,7 @@ class BullishDb(BearishDb, BullishDbBase): # type: ignore
|
|
|
389
389
|
"oai_explanation": row_dict_oai.get("explanation"),
|
|
390
390
|
"oai_high_price_target": row_dict_oai.get("high_price_target"),
|
|
391
391
|
"oai_low_price_target": row_dict_oai.get("low_price_target"),
|
|
392
|
+
"oai_moat": row_dict_oai.get("moat"),
|
|
392
393
|
}
|
|
393
394
|
|
|
394
395
|
return SubjectAnalysis.model_validate(row_dict)
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bullishpy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.70.0
|
|
4
4
|
Summary:
|
|
5
|
+
License-File: LICENSE
|
|
5
6
|
Author: aan
|
|
6
7
|
Author-email: andoludovic.andriamamonjy@gmail.com
|
|
7
8
|
Requires-Python: >=3.12,<3.13
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
-
Requires-Dist: bearishpy (>=0.
|
|
11
|
+
Requires-Dist: bearishpy (>=0.29.0,<0.30.0)
|
|
11
12
|
Requires-Dist: click (>=7.0,<=8.1)
|
|
12
13
|
Requires-Dist: huey (>=2.5.3,<3.0.0)
|
|
13
14
|
Requires-Dist: joblib (>=1.5.1,<2.0.0)
|
|
14
|
-
Requires-Dist: pandas-ta (>=0.
|
|
15
|
+
Requires-Dist: pandas-ta (>=0.4.71b0,<0.5.0)
|
|
15
16
|
Requires-Dist: plotly (>=4.12.0,<6.0.0)
|
|
16
17
|
Requires-Dist: streamlit (>=1.45.1,<2.0.0)
|
|
17
18
|
Requires-Dist: streamlit-file-browser (>=3.2.22,<4.0.0)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
bullish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
bullish/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
bullish/analysis/analysis.py,sha256=
|
|
3
|
+
bullish/analysis/analysis.py,sha256=3lZSLpTe4lFBGNrCpXmYBZhwD7o0rXSbQEkLpxJLylA,25130
|
|
4
4
|
bullish/analysis/backtest.py,sha256=x91ek5kOzJHvYq0TmJh1Q8wBDDduIaieE0zDaoZFXew,14325
|
|
5
5
|
bullish/analysis/constants.py,sha256=j3vQwjGhY-4dEEV-TkeKMDUTo2GM7M97Hcpi19LDcFQ,11458
|
|
6
6
|
bullish/analysis/filter.py,sha256=VvQALnYNyYylXkorYR3oGhsF4L_sAUSE7-aop4Trp9o,9326
|
|
7
|
-
bullish/analysis/functions.py,sha256=
|
|
8
|
-
bullish/analysis/indicators.py,sha256=
|
|
7
|
+
bullish/analysis/functions.py,sha256=jw1Tc-YtoyobYhC6AWJH-xXgaczwDZMTfQIES6Y_8qM,15780
|
|
8
|
+
bullish/analysis/indicators.py,sha256=CcDu8mu1jOOS5-3gNHYA9qDA3Ua-6PGUyoio2bDIe48,28435
|
|
9
9
|
bullish/analysis/industry_views.py,sha256=-B4CCAYz2arGQtWTXLLMpox0loO_MGdVQd2ycCRMOQQ,6799
|
|
10
|
-
bullish/analysis/openai.py,sha256=
|
|
10
|
+
bullish/analysis/openai.py,sha256=Fw7A8lFMgSEQFA48Q9GjVpEC3oiBgSHUFi7YO5rzhAc,3444
|
|
11
11
|
bullish/analysis/predefined_filters.py,sha256=E65qrTSaDFuUxoaeZ8D72K5AobumobpQdpcTIF308D4,14053
|
|
12
12
|
bullish/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
bullish/app/app.py,sha256=dnTzlyKrG2XTKDeLnJwfdvIf24eXM8fd29bvJWmMr8k,17190
|
|
@@ -28,6 +28,8 @@ bullish/database/alembic/versions/49c83f9eb5ac_.py,sha256=kCBItp7KmqpJ03roy5ikQj
|
|
|
28
28
|
bullish/database/alembic/versions/4b0a2f40b7d3_.py,sha256=G0K7w7pOPYjPZkXTB8LWhxoxuWBPcPwOfnubTBtdeEY,1827
|
|
29
29
|
bullish/database/alembic/versions/4ee82b171449_.py,sha256=QtPy5VyZPyZxS7MVkk_wGi3C44PVDoHyJ-9m9fWdqqc,1047
|
|
30
30
|
bullish/database/alembic/versions/5b10ee7604c1_.py,sha256=YlqaagPasR3RKASv7acME1jPS8p26VoTE2BvpOwdCpY,1463
|
|
31
|
+
bullish/database/alembic/versions/65662e214031_.py,sha256=Yq3lOW6liYTYiBaPRcFqVjn3k5z1mWIUXT17bv9ZroY,1596
|
|
32
|
+
bullish/database/alembic/versions/660897c02c00_.py,sha256=Sc_4uJAGheebijw3WzFNHclcWz0YF8vaZKEmVBwglDc,1033
|
|
31
33
|
bullish/database/alembic/versions/6d252e23f543_.py,sha256=izF-ejdXk733INkAokGqjA2U_M0_c1f_ruihZ-cgP7s,1525
|
|
32
34
|
bullish/database/alembic/versions/73564b60fe24_.py,sha256=MTlDRDNHj3E9gK7IMeAzv2UxxxYtWiu3gI_9xTLE-wg,1008
|
|
33
35
|
bullish/database/alembic/versions/79bc71ec6f9e_.py,sha256=4nShut2NEd1F3piSckIIBtke0GEsFAxYw5TZl5YYRzc,1140
|
|
@@ -43,7 +45,7 @@ bullish/database/alembic/versions/ec25c8fa449f_.py,sha256=8Yts74KEjK4jg20zIo90_0
|
|
|
43
45
|
bullish/database/alembic/versions/ee5baabb35f8_.py,sha256=nBMEY-_C8AsSXVPyaDdUkwrFFo2gxShzJhmrjejDwtc,1632
|
|
44
46
|
bullish/database/alembic/versions/fc191121f522_.py,sha256=0sstF6TpAJ09-Mt-Vek9SdSWksvi4C58a5D92rBtuY8,1894
|
|
45
47
|
bullish/database/alembic/versions/ff0cc4ba40ec_.py,sha256=74lxga54ig_LoNZYK9toJL9iRwGbNRezh1zvO1YI40U,2719
|
|
46
|
-
bullish/database/crud.py,sha256=
|
|
48
|
+
bullish/database/crud.py,sha256=3hx12D0gxnp-helhD6SX5zArCdTX-1FS8g5fYb1KySg,15946
|
|
47
49
|
bullish/database/schemas.py,sha256=HudFJ9lsIkVaEYjQUWammrsDnYSmEe4hOCbim3dN_4A,3946
|
|
48
50
|
bullish/database/scripts/create_revision.py,sha256=rggIf-3koPqJNth8FIg89EOfnIM7a9QrvL8X7UJsP0g,628
|
|
49
51
|
bullish/database/scripts/stamp.py,sha256=PWgVUEBumjNUMjTnGw46qmU3p221LeN-KspnW_gFuu4,839
|
|
@@ -60,8 +62,8 @@ bullish/jobs/models.py,sha256=rBXxtGFBpgZprrxq5_X2Df-bh8BLYEfw-VLMRucrqa8,784
|
|
|
60
62
|
bullish/jobs/tasks.py,sha256=13oK53fBXd5pjMLLMeOoQ4vG-yH-6dfrodhb9KzAYVw,7230
|
|
61
63
|
bullish/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
64
|
bullish/utils/checks.py,sha256=g-5QXNWNe1_BwHKrc2PtvPiLraL0tqGgxnzG7u-Wkgo,2189
|
|
63
|
-
bullishpy-0.
|
|
64
|
-
bullishpy-0.
|
|
65
|
-
bullishpy-0.
|
|
66
|
-
bullishpy-0.
|
|
67
|
-
bullishpy-0.
|
|
65
|
+
bullishpy-0.70.0.dist-info/METADATA,sha256=BSKeW7WON1LGGuhloVa1J1GYUech--iexKJp4hfrsCY,3031
|
|
66
|
+
bullishpy-0.70.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
67
|
+
bullishpy-0.70.0.dist-info/entry_points.txt,sha256=eaPpmL6vmSBFo0FBtwibCXGqAW4LFJ83whJzT1VjD-0,43
|
|
68
|
+
bullishpy-0.70.0.dist-info/licenses/LICENSE,sha256=nYb7AJFegu6ndlQhbbk54MjT-GH-0x9RF6Ls-ggJ_g4,1075
|
|
69
|
+
bullishpy-0.70.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|