bullishpy 0.4.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/__init__.py +0 -0
- bullish/analysis/__init__.py +0 -0
- bullish/analysis/analysis.py +608 -0
- bullish/analysis/filter.py +123 -0
- bullish/app/__init__.py +0 -0
- bullish/app/app.py +218 -0
- bullish/cli.py +72 -0
- bullish/database/__init__.py +0 -0
- bullish/database/alembic/README +1 -0
- bullish/database/alembic/alembic.ini +117 -0
- bullish/database/alembic/env.py +80 -0
- bullish/database/alembic/script.py.mako +26 -0
- bullish/database/alembic/versions/037dbd721317_.py +148 -0
- bullish/database/alembic/versions/4b0a2f40b7d3_.py +50 -0
- bullish/database/alembic/versions/73564b60fe24_.py +37 -0
- bullish/database/crud.py +146 -0
- bullish/database/schemas.py +33 -0
- bullish/database/scripts/create_revision.py +20 -0
- bullish/database/scripts/stamp.py +25 -0
- bullish/database/scripts/upgrade.py +27 -0
- bullish/database/settings.py +4 -0
- bullish/exceptions.py +2 -0
- bullish/figures/__init__.py +0 -0
- bullish/figures/figures.py +107 -0
- bullish/interface/__init__.py +0 -0
- bullish/interface/interface.py +96 -0
- bullish/jobs/__init__.py +0 -0
- bullish/jobs/app.py +3 -0
- bullish/jobs/models.py +25 -0
- bullish/jobs/tasks.py +77 -0
- bullishpy-0.4.0.dist-info/METADATA +22 -0
- bullishpy-0.4.0.dist-info/RECORD +34 -0
- bullishpy-0.4.0.dist-info/WHEEL +4 -0
- bullishpy-0.4.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import logging
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from bearish.interface.interface import BearishDbBase # type: ignore
|
|
7
|
+
from bearish.models.base import Ticker # type: ignore
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from bullish.analysis.analysis import Analysis, AnalysisView
|
|
11
|
+
from bullish.analysis.filter import FilterQuery, FilteredResults
|
|
12
|
+
from bullish.jobs.models import JobTracker, JobTrackerStatus, add_icons
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BullishDbBase(BearishDbBase): # type: ignore
|
|
18
|
+
def write_analysis(self, analysis: "Analysis") -> None:
|
|
19
|
+
return self._write_analysis(analysis)
|
|
20
|
+
|
|
21
|
+
def read_analysis(self, ticker: Ticker) -> Optional["Analysis"]:
|
|
22
|
+
return self._read_analysis(ticker)
|
|
23
|
+
|
|
24
|
+
def read_filter_query(self, query: FilterQuery) -> pd.DataFrame:
|
|
25
|
+
|
|
26
|
+
if not set(query.query_parameters).issubset(set(Analysis.model_fields)):
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Query parameters {query.query_parameters} are not a "
|
|
29
|
+
f"subset of Analysis model fields {Analysis.model_fields}"
|
|
30
|
+
)
|
|
31
|
+
query_ = query.to_query()
|
|
32
|
+
fields = ",".join(list(AnalysisView.model_fields))
|
|
33
|
+
query_str: str = f"""
|
|
34
|
+
SELECT {fields} FROM analysis WHERE {query_} LIMIT 1000
|
|
35
|
+
""" # noqa: S608
|
|
36
|
+
return self._read_filter_query(query_str)
|
|
37
|
+
|
|
38
|
+
def read_analysis_data(
|
|
39
|
+
self, columns: Optional[List[str]] = None, symbols: Optional[List[str]] = None
|
|
40
|
+
) -> pd.DataFrame:
|
|
41
|
+
columns = columns or list(AnalysisView.model_fields)
|
|
42
|
+
data = self._read_analysis_data(columns, symbols=symbols)
|
|
43
|
+
if set(data.columns) != set(columns):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Expected columns {columns}, but got {data.columns.tolist()}"
|
|
46
|
+
)
|
|
47
|
+
return data
|
|
48
|
+
|
|
49
|
+
def read_job_trackers(self) -> pd.DataFrame:
|
|
50
|
+
return add_icons(self._read_job_trackers())
|
|
51
|
+
|
|
52
|
+
@abc.abstractmethod
|
|
53
|
+
def _read_job_trackers(self) -> pd.DataFrame:
|
|
54
|
+
...
|
|
55
|
+
|
|
56
|
+
@abc.abstractmethod
|
|
57
|
+
def write_job_tracker(self, job_tracker: JobTracker) -> None:
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
@abc.abstractmethod
|
|
61
|
+
def delete_job_trackers(self, job_ids: List[str]) -> None:
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
@abc.abstractmethod
|
|
65
|
+
def update_job_tracker_status(self, job_tracker_status: JobTrackerStatus) -> None:
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@abc.abstractmethod
|
|
69
|
+
def _write_analysis(self, analysis: "Analysis") -> None:
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
@abc.abstractmethod
|
|
73
|
+
def _read_analysis(self, ticker: Ticker) -> Optional["Analysis"]:
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
@abc.abstractmethod
|
|
77
|
+
def _read_filter_query(self, query: str) -> pd.DataFrame:
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
@abc.abstractmethod
|
|
81
|
+
def _read_analysis_data(
|
|
82
|
+
self, columns: List[str], symbols: Optional[List[str]] = None
|
|
83
|
+
) -> pd.DataFrame:
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
@abc.abstractmethod
|
|
87
|
+
def read_filtered_results(self, name: str) -> Optional[FilteredResults]:
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
@abc.abstractmethod
|
|
91
|
+
def read_list_filtered_results(self) -> List[str]:
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
@abc.abstractmethod
|
|
95
|
+
def write_filtered_results(self, filtered_results: FilteredResults) -> None:
|
|
96
|
+
...
|
bullish/jobs/__init__.py
ADDED
|
File without changes
|
bullish/jobs/app.py
ADDED
bullish/jobs/models.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Literal, get_args
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
JobType = Literal["Update data", "Update analysis", "Fetching news"]
|
|
8
|
+
JobStatus = Literal["Completed", "Failed", "Running", "Started"]
|
|
9
|
+
StatusIcon = ["✅ Completed", "❌ Failed", "🔄 Running", "🚀 Started"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class JobTrackerStatus(BaseModel):
|
|
13
|
+
job_id: str
|
|
14
|
+
status: JobStatus = "Started"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JobTracker(JobTrackerStatus):
|
|
18
|
+
type: JobType
|
|
19
|
+
started_at: datetime = Field(default_factory=datetime.now)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def add_icons(data: pd.DataFrame) -> pd.DataFrame:
|
|
23
|
+
status_map = dict(zip(list(get_args(JobStatus)), StatusIcon, strict=True))
|
|
24
|
+
data["status"] = data["status"].map(status_map)
|
|
25
|
+
return data
|
bullish/jobs/tasks.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Optional, Any, Callable, List
|
|
4
|
+
|
|
5
|
+
from bearish.main import Bearish # type: ignore
|
|
6
|
+
from tickermood.main import get_news # type: ignore
|
|
7
|
+
from tickermood.types import DatabaseConfig # type: ignore
|
|
8
|
+
|
|
9
|
+
from .app import huey
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from huey.api import Task # type: ignore
|
|
12
|
+
|
|
13
|
+
from .models import JobTrackerStatus
|
|
14
|
+
from ..analysis.analysis import run_analysis
|
|
15
|
+
from ..database.crud import BullishDb
|
|
16
|
+
from bullish.analysis.filter import FilterUpdate
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def job_tracker(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
22
|
+
@functools.wraps(func)
|
|
23
|
+
def wrapper(
|
|
24
|
+
database_path: Path, *args: Any, task: Optional[Task] = None, **kwargs: Any
|
|
25
|
+
) -> None:
|
|
26
|
+
bullish_db = BullishDb(database_path=database_path)
|
|
27
|
+
if task is None:
|
|
28
|
+
raise ValueError("Task must be provided for job tracking.")
|
|
29
|
+
bullish_db.update_job_tracker_status(
|
|
30
|
+
JobTrackerStatus(job_id=task.id, status="Running")
|
|
31
|
+
)
|
|
32
|
+
try:
|
|
33
|
+
func(database_path, *args, task=task, **kwargs)
|
|
34
|
+
bullish_db.update_job_tracker_status(
|
|
35
|
+
JobTrackerStatus(job_id=task.id, status="Completed")
|
|
36
|
+
)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.exception(f"Fail to complete job {func.__name__}: {e}")
|
|
39
|
+
bullish_db.update_job_tracker_status(
|
|
40
|
+
JobTrackerStatus(job_id=task.id, status="Failed")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return wrapper
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@huey.task(context=True) # type: ignore
|
|
47
|
+
@job_tracker
|
|
48
|
+
def update(
|
|
49
|
+
database_path: Path,
|
|
50
|
+
symbols: List[str],
|
|
51
|
+
update_query: FilterUpdate,
|
|
52
|
+
task: Optional[Task] = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
logger.debug(f"Running update task for {len(symbols)} tickers.")
|
|
55
|
+
if not update_query.update_analysis_only:
|
|
56
|
+
bearish = Bearish(path=database_path, auto_migration=False)
|
|
57
|
+
bearish.update_prices(
|
|
58
|
+
symbols,
|
|
59
|
+
series_length=update_query.window_size,
|
|
60
|
+
delay=update_query.data_age_in_days,
|
|
61
|
+
)
|
|
62
|
+
if update_query.update_financials:
|
|
63
|
+
bearish.update_financials(symbols)
|
|
64
|
+
bullish_db = BullishDb(database_path=database_path)
|
|
65
|
+
run_analysis(bullish_db)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@huey.task(context=True) # type: ignore
|
|
69
|
+
@job_tracker
|
|
70
|
+
def news(
|
|
71
|
+
database_path: Path,
|
|
72
|
+
symbols: List[str],
|
|
73
|
+
headless: bool = True,
|
|
74
|
+
task: Optional[Task] = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
database_config = DatabaseConfig(database_path=database_path, no_migration=True)
|
|
77
|
+
get_news(symbols, database_config, headless=headless)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: bullishpy
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: aan
|
|
6
|
+
Author-email: andoludovic.andriamamonjy@gmail.com
|
|
7
|
+
Requires-Python: >=3.10,<3.13
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Requires-Dist: bearishpy (>=0.19.0,<0.20.0)
|
|
13
|
+
Requires-Dist: huey (>=2.5.3,<3.0.0)
|
|
14
|
+
Requires-Dist: pandas-ta (>=0.3.14b0,<0.4.0)
|
|
15
|
+
Requires-Dist: plotly (>=6.1.2,<7.0.0)
|
|
16
|
+
Requires-Dist: streamlit (>=1.45.1,<2.0.0)
|
|
17
|
+
Requires-Dist: streamlit-file-browser (>=3.2.22,<4.0.0)
|
|
18
|
+
Requires-Dist: streamlit-pydantic (>=v0.6.1-rc.3,<0.7.0)
|
|
19
|
+
Requires-Dist: tickermood (>=0.4.0,<0.5.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
## Bullish
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
bullish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
bullish/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
bullish/analysis/analysis.py,sha256=JqaFzWfZ_AxF-7qa7BPmSWESnrcVdSEJKmUMRFGHzew,22168
|
|
4
|
+
bullish/analysis/filter.py,sha256=rNqGGTIlaOq8Gw8mblX09bQOfR98by-NY8VY2TwP5oQ,3783
|
|
5
|
+
bullish/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
bullish/app/app.py,sha256=Ef9IfrOJelV5_oymPFtEhyW438LDw0mlK6EhZR9Yqq8,7656
|
|
7
|
+
bullish/cli.py,sha256=mxobC_49Zpn_qgXThVGhtpexjKLJS2tZZbOlo_wMLfg,1893
|
|
8
|
+
bullish/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
bullish/database/alembic/README,sha256=heMzebYwlGhnE8_4CWJ4LS74WoEZjBy-S-mIJRxAEKI,39
|
|
10
|
+
bullish/database/alembic/alembic.ini,sha256=VuwqBJV5ObTyyRNrqv8Xr-TDIRfqPjP9R1mqewYM_xE,3695
|
|
11
|
+
bullish/database/alembic/env.py,sha256=TBsN4TyVppyc2QpWqViebd4-xxUT7Cs3GDYXQdKiAMs,2260
|
|
12
|
+
bullish/database/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
|
13
|
+
bullish/database/alembic/versions/037dbd721317_.py,sha256=pbmfCcJBFwy0SH42lfYVXawDlM72bR0oUUO-0RgFmUM,8594
|
|
14
|
+
bullish/database/alembic/versions/4b0a2f40b7d3_.py,sha256=iyLvZY1Wiy7NucqxPi0TCAYD0Q8zmuFweMRZsAYx6o4,1826
|
|
15
|
+
bullish/database/alembic/versions/73564b60fe24_.py,sha256=zp9Hvr8e3FWc9SLNqiQpbA8lsd0cU8DKDScKQdQwHYA,1007
|
|
16
|
+
bullish/database/crud.py,sha256=xUSSzhHGp-KKBlG4iCNVtAEs9U5BNv4VhGSrLIR4fmM,5611
|
|
17
|
+
bullish/database/schemas.py,sha256=FjFhCa_VijAu2EEWsQ6rShSRzdH6FS9IM66ibh2kx3g,1104
|
|
18
|
+
bullish/database/scripts/create_revision.py,sha256=rggIf-3koPqJNth8FIg89EOfnIM7a9QrvL8X7UJsP0g,628
|
|
19
|
+
bullish/database/scripts/stamp.py,sha256=PWgVUEBumjNUMjTnGw46qmU3p221LeN-KspnW_gFuu4,839
|
|
20
|
+
bullish/database/scripts/upgrade.py,sha256=-Gz7aFNPEt9y9e1kltqXE76-j_8QeNtet_VlwY5AWjo,806
|
|
21
|
+
bullish/database/settings.py,sha256=nMudufmF7iC_62_PHrGSMjlqDLN2I0qTbtz9JKZHSko,164
|
|
22
|
+
bullish/exceptions.py,sha256=4z_i-dD-CDz1bkGmZH9DOf1L_awlCPCgdUDPF7dhWAI,106
|
|
23
|
+
bullish/figures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
bullish/figures/figures.py,sha256=3Ifrnl4I7gq6DiMEWZs2P0589nEsaGKYNiq-Cxj35_g,2771
|
|
25
|
+
bullish/interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
bullish/interface/interface.py,sha256=1LiTFaMI1WXgUHLf73_KPiYKbv4RFcctyPxIcvzkkf0,3082
|
|
27
|
+
bullish/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
bullish/jobs/app.py,sha256=5MJ5KXUo7JSNAvOPgkpIMasD11VTrjQvGzM7vmCY65E,77
|
|
29
|
+
bullish/jobs/models.py,sha256=ndrGTMP08S57yGLGEG9TQt8Uw2slc4HvbG-TZtEEuN0,744
|
|
30
|
+
bullish/jobs/tasks.py,sha256=vRjbCBcQciTC9283El_ji7BKxx40IbLcJkMbMVoE5wA,2533
|
|
31
|
+
bullishpy-0.4.0.dist-info/METADATA,sha256=EVIlGsSkq8hAovop7OSQ_tNy2csZzF6QysWCrwXElNg,772
|
|
32
|
+
bullishpy-0.4.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
33
|
+
bullishpy-0.4.0.dist-info/entry_points.txt,sha256=eaPpmL6vmSBFo0FBtwibCXGqAW4LFJ83whJzT1VjD-0,43
|
|
34
|
+
bullishpy-0.4.0.dist-info/RECORD,,
|