bullishpy 0.37.0__py3-none-any.whl → 0.38.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.

@@ -1,6 +1,7 @@
1
+ import json
1
2
  import logging
2
3
  import time
3
- from datetime import date
4
+ from datetime import date, datetime
4
5
  from itertools import batched, chain
5
6
  from pathlib import Path
6
7
  from typing import (
@@ -13,6 +14,7 @@ from typing import (
13
14
  get_args,
14
15
  TYPE_CHECKING,
15
16
  ClassVar,
17
+ Dict,
16
18
  )
17
19
 
18
20
  import pandas as pd
@@ -488,12 +490,49 @@ class AnalysisView(BaseModel):
488
490
  default=None,
489
491
  ),
490
492
  ]
493
+ consensus: Optional[str] = None
494
+ recommendation: Optional[str] = None
491
495
  yearly_growth: Optional[float] = None
492
496
  weekly_growth: Optional[float] = None
493
497
  monthly_growth: Optional[float] = None
494
498
 
495
499
 
496
- class Analysis(AnalysisEarningsDate, AnalysisView, BaseEquity, TechnicalAnalysis, FundamentalAnalysis): # type: ignore
500
+ def json_loads(value: Any) -> Any:
501
+ if isinstance(value, str):
502
+ try:
503
+ return json.loads(value)
504
+ except Exception as e:
505
+ logger.debug(e)
506
+ return None
507
+ return value
508
+
509
+
510
+ class SubjectAnalysis(BaseModel):
511
+ high_price_target: Optional[float] = None
512
+ low_price_target: Optional[float] = None
513
+ consensus: Optional[str] = None
514
+ reason: Optional[str] = None
515
+ recommendation: Optional[str] = None
516
+ explanation: Optional[str] = None
517
+ news_date: Optional[datetime] = None
518
+ news_summary: Annotated[
519
+ Optional[List[Dict[str, Any]]], BeforeValidator(json_loads)
520
+ ] = None
521
+ summary: Annotated[Optional[Dict[str, Any]], BeforeValidator(json_loads)] = None
522
+
523
+ def to_news(self) -> Optional[str]:
524
+ if not self.news_summary:
525
+ return None
526
+ return "".join(
527
+ [
528
+ f"<p>{t.get('content')}</p>"
529
+ for t in self.news_summary
530
+ if t.get("content")
531
+ ]
532
+ )
533
+
534
+
535
+ class Analysis(SubjectAnalysis, AnalysisEarningsDate, AnalysisView, BaseEquity, TechnicalAnalysis, FundamentalAnalysis): # type: ignore
497
536
 
498
537
  @classmethod
499
538
  def from_ticker(cls, bearish_db: "BullishDb", ticker: Ticker) -> "Analysis":
@@ -503,16 +542,20 @@ class Analysis(AnalysisEarningsDate, AnalysisView, BaseEquity, TechnicalAnalysis
503
542
  excluded_sources=get_args(TickerOnlySources),
504
543
  )
505
544
  )
545
+
506
546
  equity = asset.get_one_equity()
507
547
  financials = Financials.from_ticker(bearish_db, ticker)
508
548
  fundamental_analysis = FundamentalAnalysis.from_financials(financials, ticker)
509
549
  prices = Prices.from_ticker(bearish_db, ticker)
510
550
  technical_analysis = TechnicalAnalysis.from_data(prices.to_dataframe(), ticker)
511
551
  next_earnings_date = bearish_db.read_next_earnings_date(ticker.symbol)
552
+ subject = bearish_db.read_subject(ticker.symbol)
553
+
512
554
  return cls.model_validate(
513
555
  equity.model_dump()
514
556
  | fundamental_analysis.model_dump()
515
557
  | technical_analysis.model_dump()
558
+ | (subject.model_dump() if subject else {})
516
559
  | {
517
560
  "next_earnings_date": next_earnings_date,
518
561
  "price_per_earning_ratio": (
bullish/app/app.py CHANGED
@@ -91,11 +91,13 @@ def on_table_select() -> None:
91
91
  prices = db.read_series(query, months=24)
92
92
  data = Prices(prices=prices).to_dataframe()
93
93
  dates = db.read_dates(symbol)
94
+ subject = db.read_subject(symbol)
94
95
  industry_data = get_industry_comparison_data(db, data, "Mean", industry, country)
95
96
 
96
97
  fig = plot(data, symbol, dates=dates, industry_data=industry_data)
97
98
 
98
99
  st.session_state.ticker_figure = fig
100
+ st.session_state.ticker_news = subject
99
101
 
100
102
 
101
103
  @st.dialog("🔑 Provide database file to continue")
@@ -278,6 +280,7 @@ def filter() -> None:
278
280
  st.session_state.database_path
279
281
  ).read_filter_query(query)
280
282
  st.session_state.ticker_figure = None
283
+ st.session_state.ticker_news = None
281
284
  st.session_state.filter_query = {}
282
285
  st.session_state.query = query
283
286
  st.rerun()
@@ -297,8 +300,87 @@ def dialog_plot_figure() -> None:
297
300
  unsafe_allow_html=True,
298
301
  )
299
302
  st.html("<span class='big-dialog'></span>")
303
+ if st.session_state.ticker_news:
304
+ st.markdown(
305
+ f"""
306
+ <div class="news-hover" >
307
+ 📰 <span class="label">News</span>
308
+ <div class="tooltip">
309
+ <h2>Date: {st.session_state.ticker_news.news_date.date()}</h2>
310
+ <h2>Price targets</h2>
311
+ <p>High price target: {st.session_state.ticker_news.high_price_target}</p>
312
+ <p>Low price target: {st.session_state.ticker_news.low_price_target}</p>
313
+ <h2>Recommendation: {st.session_state.ticker_news.recommendation}</h2>
314
+ <h2>Consensus: {st.session_state.ticker_news.consensus}</h2>
315
+ <h2>Explanation & reasons</h2>
316
+ <p>{st.session_state.ticker_news.explanation}</p>
317
+ <p>{st.session_state.ticker_news.reason}</p>
318
+ <h2>News summaries</h2>
319
+ {st.session_state.ticker_news.to_news()}
320
+ </div>
321
+ </div>
322
+ <style>
323
+ /* Hover target (fixed top-left) */
324
+ .news-hover {{
325
+ position: absolute;
326
+ left: 1rem;
327
+ display: inline-flex;
328
+ align-items: center;
329
+ gap: .4rem;
330
+ font-size: 1.7rem; /* big label */
331
+ font-weight: 600;
332
+ color: #333;
333
+ cursor: pointer;
334
+ user-select: none;
335
+ z-index: 1100;
336
+ }}
337
+ /* Tooltip bubble */
338
+ .news-hover .tooltip {{
339
+ position: absolute;
340
+ top: 110%; /* below the label */
341
+ left: 0;
342
+ width: 840px;
343
+ max-height: 620px;
344
+ overflow-y: auto;
345
+ background: #222;
346
+ color: #fff;
347
+ padding: 1.2rem;
348
+ border-radius: 10px;
349
+ font-size: .95rem;
350
+ line-height: 1.45;
351
+ box-shadow: 0 8px 20px rgba(0,0,0,.4);
352
+ opacity: 0;
353
+ visibility: hidden;
354
+ transition: opacity .25s ease;
355
+ }}
356
+ .news-hover .tooltip hr {{
357
+ border: none;
358
+ border-top: 1px solid #444;
359
+ margin: 1rem 0;
360
+ }}
361
+ /* Show tooltip on hover or keyboard focus */
362
+ .news-hover:hover .tooltip,
363
+ .news-hover:focus-within .tooltip {{
364
+ opacity: 1;
365
+ visibility: visible;
366
+ }}
367
+ /* Little arrow under the bubble */
368
+ .news-hover .tooltip::after {{
369
+ content: "";
370
+ position: absolute;
371
+ top: -10px;
372
+ left: 20px;
373
+ border: 10px solid transparent;
374
+ border-top-color: #222;
375
+ }}
376
+ </style>
377
+ """,
378
+ unsafe_allow_html=True,
379
+ )
380
+
300
381
  st.plotly_chart(st.session_state.ticker_figure, use_container_width=True)
301
382
  st.session_state.ticker_figure = None
383
+ st.session_state.ticker_news = None
302
384
 
303
385
 
304
386
  @st.dialog("⭐ Save filtered results")
@@ -0,0 +1,96 @@
1
+ """
2
+
3
+ Revision ID: ae444f338124
4
+ Revises: d0e58e050845
5
+ Create Date: 2025-08-07 11:07:48.169941
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
+ import sqlmodel
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "ae444f338124"
18
+ down_revision: Union[str, None] = "d0e58e050845"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ with op.batch_alter_table("analysis", schema=None) as batch_op:
26
+ batch_op.add_column(
27
+ sa.Column("consensus", sqlmodel.sql.sqltypes.AutoString(), nullable=True)
28
+ )
29
+ batch_op.add_column(
30
+ sa.Column(
31
+ "recommendation", sqlmodel.sql.sqltypes.AutoString(), nullable=True
32
+ )
33
+ )
34
+ batch_op.add_column(sa.Column("high_price_target", sa.Float(), nullable=True))
35
+ batch_op.add_column(sa.Column("low_price_target", sa.Float(), nullable=True))
36
+ batch_op.add_column(
37
+ sa.Column("reason", sqlmodel.sql.sqltypes.AutoString(), nullable=True)
38
+ )
39
+ batch_op.add_column(
40
+ sa.Column("explanation", sqlmodel.sql.sqltypes.AutoString(), nullable=True)
41
+ )
42
+ batch_op.add_column(sa.Column("news_date", sa.DateTime(), nullable=True))
43
+ batch_op.add_column(sa.Column("news_summary", sa.JSON(), nullable=True))
44
+ batch_op.alter_column(
45
+ "summary",
46
+ existing_type=sa.VARCHAR(),
47
+ type_=sa.JSON(),
48
+ existing_nullable=True,
49
+ )
50
+ batch_op.create_index("ix_analysis_consensus", ["consensus"], unique=False)
51
+ batch_op.create_index("ix_analysis_explanation", ["explanation"], unique=False)
52
+ batch_op.create_index(
53
+ "ix_analysis_high_price_target", ["high_price_target"], unique=False
54
+ )
55
+ batch_op.create_index(
56
+ "ix_analysis_low_price_target", ["low_price_target"], unique=False
57
+ )
58
+ batch_op.create_index("ix_analysis_news_date", ["news_date"], unique=False)
59
+ batch_op.create_index(
60
+ "ix_analysis_news_summary", ["news_summary"], unique=False
61
+ )
62
+ batch_op.create_index("ix_analysis_reason", ["reason"], unique=False)
63
+ batch_op.create_index(
64
+ "ix_analysis_recommendation", ["recommendation"], unique=False
65
+ )
66
+
67
+ # ### end Alembic commands ###
68
+
69
+
70
+ def downgrade() -> None:
71
+ # ### commands auto generated by Alembic - please adjust! ###
72
+ with op.batch_alter_table("analysis", schema=None) as batch_op:
73
+ batch_op.drop_index("ix_analysis_recommendation")
74
+ batch_op.drop_index("ix_analysis_reason")
75
+ batch_op.drop_index("ix_analysis_news_summary")
76
+ batch_op.drop_index("ix_analysis_news_date")
77
+ batch_op.drop_index("ix_analysis_low_price_target")
78
+ batch_op.drop_index("ix_analysis_high_price_target")
79
+ batch_op.drop_index("ix_analysis_explanation")
80
+ batch_op.drop_index("ix_analysis_consensus")
81
+ batch_op.alter_column(
82
+ "summary",
83
+ existing_type=sa.JSON(),
84
+ type_=sa.VARCHAR(),
85
+ existing_nullable=True,
86
+ )
87
+ batch_op.drop_column("news_summary")
88
+ batch_op.drop_column("news_date")
89
+ batch_op.drop_column("explanation")
90
+ batch_op.drop_column("reason")
91
+ batch_op.drop_column("low_price_target")
92
+ batch_op.drop_column("high_price_target")
93
+ batch_op.drop_column("recommendation")
94
+ batch_op.drop_column("consensus")
95
+
96
+ # ### end Alembic commands ###
bullish/database/crud.py CHANGED
@@ -7,21 +7,21 @@ from typing import TYPE_CHECKING, Any, List, Optional
7
7
 
8
8
  import pandas as pd
9
9
  from bearish.database.crud import BearishDb # type: ignore
10
- from bearish.models.base import Ticker # type: ignore
11
10
  from bearish.database.schemas import EarningsDateORM, EquityORM, PriceORM # type: ignore
12
- from bearish.types import Sources # type: ignore
11
+ from bearish.models.base import Ticker # type: ignore
13
12
  from bearish.models.price.price import Price # type: ignore
14
13
  from bearish.models.price.prices import Prices # type: ignore
14
+ from bearish.types import Sources # type: ignore
15
15
  from pydantic import ConfigDict
16
16
  from sqlalchemy import Engine, create_engine, insert, delete, update
17
+ from sqlalchemy import text
17
18
  from sqlmodel import Session, select
18
19
 
19
- from bullish.analysis.analysis import Analysis
20
-
20
+ from bullish.analysis.analysis import Analysis, SubjectAnalysis
21
21
  from bullish.analysis.constants import Industry, IndustryGroup, Sector, Country
22
+ from bullish.analysis.filter import FilteredResults
22
23
  from bullish.analysis.indicators import SignalSeries
23
24
  from bullish.analysis.industry_views import Type, IndustryView
24
-
25
25
  from bullish.database.schemas import (
26
26
  AnalysisORM,
27
27
  JobTrackerORM,
@@ -32,7 +32,6 @@ from bullish.database.schemas import (
32
32
  )
33
33
  from bullish.database.scripts.upgrade import upgrade
34
34
  from bullish.exceptions import DatabaseFileNotFoundError
35
- from bullish.analysis.filter import FilteredResults
36
35
  from bullish.interface.interface import BullishDbBase
37
36
  from bullish.jobs.models import JobTracker, JobTrackerStatus
38
37
 
@@ -341,3 +340,22 @@ class BullishDb(BearishDb, BullishDbBase): # type: ignore
341
340
  if result:
342
341
  return result.date() # type: ignore
343
342
  return None
343
+
344
+ def read_subject(self, symbol: str) -> Optional[SubjectAnalysis]:
345
+ sql = text(
346
+ """
347
+ SELECT *
348
+ FROM subject
349
+ WHERE symbol = :symbol
350
+ ORDER BY date DESC
351
+ LIMIT 1
352
+ """
353
+ )
354
+
355
+ with Session(self._engine) as session:
356
+ row = session.execute(sql, {"symbol": symbol}).mappings().one_or_none()
357
+ if row:
358
+ row_dict = dict(row)
359
+ row_dict = row_dict | {"news_date": row_dict["date"]}
360
+ return SubjectAnalysis.model_validate(row_dict)
361
+ return None
@@ -1,4 +1,4 @@
1
- from typing import Dict, Any
1
+ from typing import Dict, Any, List, Optional
2
2
 
3
3
  from sqlmodel import Field, SQLModel
4
4
  from sqlalchemy import Column, JSON
@@ -25,6 +25,10 @@ dynamic_indexes = tuple(
25
25
  class AnalysisORM(BaseTable, Analysis, table=True):
26
26
  __tablename__ = "analysis"
27
27
  __table_args__ = {"extend_existing": True} # noqa:RUF012
28
+ news_summary: Optional[List[Dict[str, Any]]] = Field(
29
+ default=None, sa_column=Column(JSON)
30
+ )
31
+ summary: Optional[Dict[str, Any]] = Field(default=None, sa_column=Column(JSON))
28
32
 
29
33
 
30
34
  AnalysisORM.__table_args__ = tuple( # type: ignore # noqa: RUF005
@@ -9,7 +9,7 @@ from bearish.models.base import Ticker # type: ignore
9
9
  from bearish.types import Sources # type: ignore
10
10
 
11
11
 
12
- from bullish.analysis.analysis import Analysis, AnalysisView
12
+ from bullish.analysis.analysis import Analysis, AnalysisView, SubjectAnalysis
13
13
  from bullish.analysis.backtest import BacktestResult, BacktestResultQuery
14
14
  from bullish.analysis.constants import Industry, Sector, IndustryGroup, Country
15
15
  from bullish.analysis.filter import FilterQuery, FilteredResults
@@ -152,3 +152,6 @@ class BullishDbBase(BearishDbBase): # type: ignore
152
152
 
153
153
  @abc.abstractmethod
154
154
  def read_next_earnings_date(self, symbol: str) -> Optional[date]: ...
155
+
156
+ @abc.abstractmethod
157
+ def read_subject(self, symbol: str) -> Optional[SubjectAnalysis]: ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bullishpy
3
- Version: 0.37.0
3
+ Version: 0.38.0
4
4
  Summary:
5
5
  Author: aan
6
6
  Author-email: andoludovic.andriamamonjy@gmail.com
@@ -1,6 +1,6 @@
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=nCb___Tb2aOFIu1wWtPwS1VVZfXOSCp5zUWaIOCdvl4,22345
3
+ bullish/analysis/analysis.py,sha256=O7bTqZfgRBj3jMXhcm8COy8M5u9rUQuKKMBsicr1N70,23600
4
4
  bullish/analysis/backtest.py,sha256=x91ek5kOzJHvYq0TmJh1Q8wBDDduIaieE0zDaoZFXew,14325
5
5
  bullish/analysis/constants.py,sha256=X3oCyYNA6B-jsZSYJLeGQ94S453Z7jIVNPmv3lMPp8Q,9922
6
6
  bullish/analysis/filter.py,sha256=mGWizTda_QHEmusDmB_xvOhjlsOKcbILfKLjJLAnCnE,9285
@@ -9,7 +9,7 @@ bullish/analysis/indicators.py,sha256=XsMHc4-hEZwxFpI3JI-s4C2hcg0eCQLWcAQ8P46dtL
9
9
  bullish/analysis/industry_views.py,sha256=-B4CCAYz2arGQtWTXLLMpox0loO_MGdVQd2ycCRMOQQ,6799
10
10
  bullish/analysis/predefined_filters.py,sha256=kx3vhtvGu_0ySWsWPzkqXONmB7COWgMowv3TEVrk1Uc,8198
11
11
  bullish/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- bullish/app/app.py,sha256=9zxskm5gHt2uDGn59vL4ltI2zgrIBGhavp7jogt1us0,14411
12
+ bullish/app/app.py,sha256=37RPSxKkncP9yzrlJtTr9xkgf7PLdOByN-hyERF3Y5Q,17554
13
13
  bullish/cli.py,sha256=azhVLwOUrmwrtFAJSgva8-UFgNgkepXhjp7DxQNc-yw,2427
14
14
  bullish/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  bullish/database/alembic/README,sha256=heMzebYwlGhnE8_4CWJ4LS74WoEZjBy-S-mIJRxAEKI,39
@@ -28,6 +28,7 @@ bullish/database/alembic/versions/5b10ee7604c1_.py,sha256=YlqaagPasR3RKASv7acME1
28
28
  bullish/database/alembic/versions/6d252e23f543_.py,sha256=izF-ejdXk733INkAokGqjA2U_M0_c1f_ruihZ-cgP7s,1525
29
29
  bullish/database/alembic/versions/73564b60fe24_.py,sha256=MTlDRDNHj3E9gK7IMeAzv2UxxxYtWiu3gI_9xTLE-wg,1008
30
30
  bullish/database/alembic/versions/79bc71ec6f9e_.py,sha256=4nShut2NEd1F3piSckIIBtke0GEsFAxYw5TZl5YYRzc,1140
31
+ bullish/database/alembic/versions/ae444f338124_.py,sha256=u8RphcniLCQce-HvN666QgCJpLsv6A91-a4R-Nif4bU,3672
31
32
  bullish/database/alembic/versions/b76079e9845f_.py,sha256=W8eeTABjI9tT1dp3hlK7g7tiKqDhmA8AoUX9Sw-ykLI,1165
32
33
  bullish/database/alembic/versions/bf6b86dd5463_.py,sha256=fKB8knCprGmiL6AEyFdhybVmB7QX_W4MPFF9sPzUrSM,1094
33
34
  bullish/database/alembic/versions/d0e58e050845_.py,sha256=x_LS3J27FNyy_WD99uvZzNehly-jpgn9abOYN-VjjZc,1164
@@ -36,8 +37,8 @@ bullish/database/alembic/versions/ec25c8fa449f_.py,sha256=8Yts74KEjK4jg20zIo90_0
36
37
  bullish/database/alembic/versions/ee5baabb35f8_.py,sha256=nBMEY-_C8AsSXVPyaDdUkwrFFo2gxShzJhmrjejDwtc,1632
37
38
  bullish/database/alembic/versions/fc191121f522_.py,sha256=0sstF6TpAJ09-Mt-Vek9SdSWksvi4C58a5D92rBtuY8,1894
38
39
  bullish/database/alembic/versions/ff0cc4ba40ec_.py,sha256=74lxga54ig_LoNZYK9toJL9iRwGbNRezh1zvO1YI40U,2719
39
- bullish/database/crud.py,sha256=TvA3TGjr6RNYdNP0u-7RmVU1i9mqNbsv8lOcRbaXbn8,13122
40
- bullish/database/schemas.py,sha256=3uRcNKuobqWC3mCfInzo-4KhrZp3DH6yx_0TEbLoHME,3428
40
+ bullish/database/crud.py,sha256=8qc1WqL3AR9_7TkIDMB_icbXghl1DgJnxVxPnopHPho,13778
41
+ bullish/database/schemas.py,sha256=fQ4RZeOjlFoIor7rjwpisbHRNDd7-zbyDdzNKaiNGQQ,3637
41
42
  bullish/database/scripts/create_revision.py,sha256=rggIf-3koPqJNth8FIg89EOfnIM7a9QrvL8X7UJsP0g,628
42
43
  bullish/database/scripts/stamp.py,sha256=PWgVUEBumjNUMjTnGw46qmU3p221LeN-KspnW_gFuu4,839
43
44
  bullish/database/scripts/upgrade.py,sha256=-Gz7aFNPEt9y9e1kltqXE76-j_8QeNtet_VlwY5AWjo,806
@@ -46,14 +47,14 @@ bullish/exceptions.py,sha256=4z_i-dD-CDz1bkGmZH9DOf1L_awlCPCgdUDPF7dhWAI,106
46
47
  bullish/figures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
48
  bullish/figures/figures.py,sha256=EpJQOiSqSp7cHvZoGlZrF6UVpyv-fFyDApAfskqdUkU,4562
48
49
  bullish/interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- bullish/interface/interface.py,sha256=dFQW0tMYbFL-gWrlWTWP1qKKSzqlrhz6-T_lLqhILyw,5134
50
+ bullish/interface/interface.py,sha256=R2qVEMyBl9mBlPUO40zXp4vhfLKH7pgl_u2BmAVlD4w,5250
50
51
  bullish/jobs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
52
  bullish/jobs/app.py,sha256=5MJ5KXUo7JSNAvOPgkpIMasD11VTrjQvGzM7vmCY65E,77
52
53
  bullish/jobs/models.py,sha256=S2yvBf69lmt4U-5OU5CjXCMSw0s9Ubh9xkrB3k2qOZo,764
53
54
  bullish/jobs/tasks.py,sha256=jqiTTe9PV4x9ebV5QPRBiGyANhOoAymigYqs44WWH9k,3714
54
55
  bullish/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
56
  bullish/utils/checks.py,sha256=Va10_xDVVnxYkOD2hafvyQ-TFV8FQpOkr4huJ7XgpDM,2188
56
- bullishpy-0.37.0.dist-info/METADATA,sha256=dalQXAyD3JnwPRmQylOJSC1o9ao5B4yJKnfguRjYAE0,830
57
- bullishpy-0.37.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
58
- bullishpy-0.37.0.dist-info/entry_points.txt,sha256=eaPpmL6vmSBFo0FBtwibCXGqAW4LFJ83whJzT1VjD-0,43
59
- bullishpy-0.37.0.dist-info/RECORD,,
57
+ bullishpy-0.38.0.dist-info/METADATA,sha256=egDaIIcxG8oKLLvNhrZETkS7JkvPX6f5Q5m7_bf49ZI,830
58
+ bullishpy-0.38.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
59
+ bullishpy-0.38.0.dist-info/entry_points.txt,sha256=eaPpmL6vmSBFo0FBtwibCXGqAW4LFJ83whJzT1VjD-0,43
60
+ bullishpy-0.38.0.dist-info/RECORD,,