bullishpy 0.13.0__py3-none-any.whl → 0.14.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,6 @@
1
1
  import datetime
2
2
  from datetime import date
3
- from typing import Literal, get_args, Any, Optional, List, Tuple, Type, Dict
3
+ from typing import get_args, Any, Optional, List, Tuple, Type, Dict
4
4
 
5
5
  from bearish.types import SeriesLength # type: ignore
6
6
  from pydantic import BaseModel, Field, ConfigDict
@@ -14,411 +14,8 @@ from bullish.analysis.analysis import (
14
14
  TechnicalAnalysis,
15
15
  AnalysisView,
16
16
  )
17
+ from bullish.analysis.constants import Industry, IndustryGroup, Sector, Country
17
18
 
18
- Industry = Literal[
19
- "Publishing",
20
- "Internet Retail",
21
- "Scientific & Technical Instruments",
22
- "Engineering & Construction",
23
- "Diagnostics & Research",
24
- "Software - Infrastructure",
25
- "Thermal Coal",
26
- "Software - Application",
27
- "Auto Manufacturers",
28
- "Farm Products",
29
- "Medical Devices",
30
- "Education & Training Services",
31
- "Auto Parts",
32
- "Specialty Chemicals",
33
- "Marine Shipping",
34
- "Biotechnology",
35
- "Real Estate Services",
36
- "Gold",
37
- "Entertainment",
38
- "Specialty Retail",
39
- "Utilities - Independent Power Producers",
40
- "Steel",
41
- "Mortgage Finance",
42
- "Communication Equipment",
43
- "Drug Manufacturers - Specialty & Generic",
44
- "Electronic Gaming & Multimedia",
45
- "Banks - Regional",
46
- "Oil & Gas E&P",
47
- "Travel Services",
48
- "Real Estate - Diversified",
49
- "Telecom Services",
50
- "Uranium",
51
- "Consulting Services",
52
- "Waste Management",
53
- "Agricultural Inputs",
54
- "Utilities - Diversified",
55
- "Auto & Truck Dealerships",
56
- "Confectioners",
57
- "Other Industrial Metals & Mining",
58
- "Beverages - Wineries & Distilleries",
59
- "Oil & Gas Midstream",
60
- "Recreational Vehicles",
61
- "Electrical Equipment & Parts",
62
- "Household & Personal Products",
63
- "Packaging & Containers",
64
- "REIT - Specialty",
65
- "Home Improvement Retail",
66
- "Electronic Components",
67
- "Asset Management",
68
- "Consumer Electronics",
69
- "Conglomerates",
70
- "Health Information Services",
71
- "Medical Instruments & Supplies",
72
- "Building Products & Equipment",
73
- "Information Technology Services",
74
- "Specialty Industrial Machinery",
75
- "Food Distribution",
76
- "Packaged Foods",
77
- "Rental & Leasing Services",
78
- "Medical Distribution",
79
- "Grocery Stores",
80
- "Advertising Agencies",
81
- "Beverages - Non - Alcoholic",
82
- "Apparel Manufacturing",
83
- "Oil & Gas Equipment & Services",
84
- "Coking Coal",
85
- "Industrial Distribution",
86
- "Restaurants",
87
- "Beverages - Brewers",
88
- "Chemicals",
89
- "Real Estate - Development",
90
- "Credit Services",
91
- "Tobacco",
92
- "Metal Fabrication",
93
- "Building Materials",
94
- "Residential Construction",
95
- "Specialty Business Services",
96
- "REIT - Hotel & Motel",
97
- "Internet Content & Information",
98
- "Lodging",
99
- "Furnishings, Fixtures & Appliances",
100
- "Airlines",
101
- "Computer Hardware",
102
- "Integrated Freight & Logistics",
103
- "Solar",
104
- "Capital Markets",
105
- "Leisure",
106
- "Airports & Air Services",
107
- "Aluminum",
108
- "Insurance Brokers",
109
- "Semiconductors",
110
- "REIT - Retail",
111
- "Luxury Goods",
112
- "Lumber & Wood Production",
113
- "REIT - Mortgage",
114
- "Semiconductor Equipment & Materials",
115
- "Aerospace & Defense",
116
- "Security & Protection Services",
117
- "Utilities - Renewable",
118
- "Utilities - Regulated Gas",
119
- "Apparel Retail",
120
- "Pollution & Treatment Controls",
121
- "Broadcasting",
122
- "Resorts & Casinos",
123
- "Other Precious Metals & Mining",
124
- "Financial Data & Stock Exchanges",
125
- "Footwear & Accessories",
126
- "Medical Care Facilities",
127
- "Electronics & Computer Distribution",
128
- "Gambling",
129
- "Tools & Accessories",
130
- "Insurance - Property & Casualty",
131
- "Utilities - Regulated Water",
132
- "Insurance - Specialty",
133
- "Personal Services",
134
- "Pharmaceutical Retailers",
135
- "Farm & Heavy Construction Machinery",
136
- "Utilities - Regulated Electric",
137
- "Department Stores",
138
- "Staffing & Employment Services",
139
- "Textile Manufacturing",
140
- "Silver",
141
- "REIT - Industrial",
142
- "REIT - Diversified",
143
- "Copper",
144
- "Business Equipment & Supplies",
145
- "Infrastructure Operations",
146
- "Trucking",
147
- "Insurance - Reinsurance",
148
- "Insurance - Diversified",
149
- "Drug Manufacturers - General",
150
- "Oil & Gas Drilling",
151
- "Banks - Diversified",
152
- "REIT - Residential",
153
- "Oil & Gas Refining & Marketing",
154
- "Shell Companies",
155
- "Financial Conglomerates",
156
- "Paper & Paper Products",
157
- "Insurance - Life",
158
- "REIT - Office",
159
- "Railroads",
160
- "Oil & Gas Integrated",
161
- "Healthcare Plans",
162
- "REIT - Healthcare Facilities",
163
- "Discount Stores",
164
- ]
165
-
166
- IndustryGroup = Literal[
167
- "publishing",
168
- "internet-retail",
169
- "scientific-technical-instruments",
170
- "engineering-construction",
171
- "diagnostics-research",
172
- "software-infrastructure",
173
- "thermal-coal",
174
- "software-application",
175
- "auto-manufacturers",
176
- "farm-products",
177
- "medical-devices",
178
- "education-training-services",
179
- "auto-parts",
180
- "specialty-chemicals",
181
- "marine-shipping",
182
- "biotechnology",
183
- "real-estate-services",
184
- "gold",
185
- "entertainment",
186
- "specialty-retail",
187
- "utilities-independent-power-producers",
188
- "steel",
189
- "mortgage-finance",
190
- "communication-equipment",
191
- "drug-manufacturers-specialty-generic",
192
- "electronic-gaming-multimedia",
193
- "banks-regional",
194
- "oil-gas-e-p",
195
- "travel-services",
196
- "real-estate-diversified",
197
- "telecom-services",
198
- "uranium",
199
- "consulting-services",
200
- "waste-management",
201
- "agricultural-inputs",
202
- "utilities-diversified",
203
- "auto-truck-dealerships",
204
- "confectioners",
205
- "other-industrial-metals-mining",
206
- "beverages-wineries-distilleries",
207
- "oil-gas-midstream",
208
- "recreational-vehicles",
209
- "electrical-equipment-parts",
210
- "household-personal-products",
211
- "packaging-containers",
212
- "reit-specialty",
213
- "home-improvement-retail",
214
- "electronic-components",
215
- "asset-management",
216
- "consumer-electronics",
217
- "conglomerates",
218
- "health-information-services",
219
- "medical-instruments-supplies",
220
- "building-products-equipment",
221
- "information-technology-services",
222
- "specialty-industrial-machinery",
223
- "food-distribution",
224
- "packaged-foods",
225
- "rental-leasing-services",
226
- "medical-distribution",
227
- "grocery-stores",
228
- "advertising-agencies",
229
- "beverages-non-alcoholic",
230
- "apparel-manufacturing",
231
- "oil-gas-equipment-services",
232
- "coking-coal",
233
- "industrial-distribution",
234
- "restaurants",
235
- "beverages-brewers",
236
- "chemicals",
237
- "real-estate-development",
238
- "credit-services",
239
- "tobacco",
240
- "metal-fabrication",
241
- "building-materials",
242
- "residential-construction",
243
- "specialty-business-services",
244
- "reit-hotel-motel",
245
- "internet-content-information",
246
- "lodging",
247
- "furnishings-fixtures-appliances",
248
- "airlines",
249
- "computer-hardware",
250
- "integrated-freight-logistics",
251
- "solar",
252
- "capital-markets",
253
- "leisure",
254
- "airports-air-services",
255
- "aluminum",
256
- "insurance-brokers",
257
- "semiconductors",
258
- "reit-retail",
259
- "luxury-goods",
260
- "lumber-wood-production",
261
- "reit-mortgage",
262
- "semiconductor-equipment-materials",
263
- "aerospace-defense",
264
- "security-protection-services",
265
- "utilities-renewable",
266
- "utilities-regulated-gas",
267
- "apparel-retail",
268
- "pollution-treatment-controls",
269
- "broadcasting",
270
- "resorts-casinos",
271
- "other-precious-metals-mining",
272
- "financial-data-stock-exchanges",
273
- "footwear-accessories",
274
- "medical-care-facilities",
275
- "electronics-computer-distribution",
276
- "gambling",
277
- "tools-accessories",
278
- "insurance-property-casualty",
279
- "utilities-regulated-water",
280
- "insurance-specialty",
281
- "personal-services",
282
- "pharmaceutical-retailers",
283
- "farm-heavy-construction-machinery",
284
- "utilities-regulated-electric",
285
- "department-stores",
286
- "staffing-employment-services",
287
- "textile-manufacturing",
288
- "silver",
289
- "reit-industrial",
290
- "reit-diversified",
291
- "copper",
292
- "business-equipment-supplies",
293
- "infrastructure-operations",
294
- "trucking",
295
- "insurance-reinsurance",
296
- "insurance-diversified",
297
- "drug-manufacturers-general",
298
- "oil-gas-drilling",
299
- "banks-diversified",
300
- "reit-residential",
301
- "oil-gas-refining-marketing",
302
- "shell-companies",
303
- "financial-conglomerates",
304
- "paper-paper-products",
305
- "insurance-life",
306
- "reit-office",
307
- "railroads",
308
- "oil-gas-integrated",
309
- "healthcare-plans",
310
- "reit-healthcare-facilities",
311
- "discount-stores",
312
- ]
313
-
314
- Sector = Literal[
315
- "Communication Services",
316
- "Consumer Cyclical",
317
- "Technology",
318
- "Industrials",
319
- "Healthcare",
320
- "Energy",
321
- "Consumer Defensive",
322
- "Basic Materials",
323
- "Real Estate",
324
- "Utilities",
325
- "Financial Services",
326
- "Conglomerates",
327
- ]
328
-
329
- Country = Literal[
330
- "Australia",
331
- "China",
332
- "Japan",
333
- "United kingdom",
334
- "United states",
335
- "Poland",
336
- "Switzerland",
337
- "Canada",
338
- "Greece",
339
- "Spain",
340
- "Germany",
341
- "Indonesia",
342
- "Belgium",
343
- "France",
344
- "Netherlands",
345
- "British virgin islands",
346
- "Italy",
347
- "Hungary",
348
- "Austria",
349
- "Finland",
350
- "Sweden",
351
- "Bermuda",
352
- "Taiwan",
353
- "Israel",
354
- "Ukraine",
355
- "Singapore",
356
- "Jersey",
357
- "Ireland",
358
- "Luxembourg",
359
- "Cyprus",
360
- "Cayman islands",
361
- "Norway",
362
- "Denmark",
363
- "Hong kong",
364
- "New zealand",
365
- "Kazakhstan",
366
- "Nigeria",
367
- "Argentina",
368
- "Brazil",
369
- "Czech republic",
370
- "Mauritius",
371
- "South africa",
372
- "India",
373
- "Mexico",
374
- "Mongolia",
375
- "Slovenia",
376
- "Thailand",
377
- "Malaysia",
378
- "Costa rica",
379
- "Isle of man",
380
- "Egypt",
381
- "Turkey",
382
- "United arab emirates",
383
- "Colombia",
384
- "Gibraltar",
385
- "Malta",
386
- "Liechtenstein",
387
- "Guernsey",
388
- "Peru",
389
- "Estonia",
390
- "French guiana",
391
- "Portugal",
392
- "Uruguay",
393
- "Chile",
394
- "Martinique",
395
- "Monaco",
396
- "Panama",
397
- "Papua new guinea",
398
- "South korea",
399
- "Macau",
400
- "Gabon",
401
- "Romania",
402
- "Senegal",
403
- "Morocco",
404
- "Jordan",
405
- "Lithuania",
406
- "Dominican republic",
407
- "Reunion",
408
- "Zambia",
409
- "Cambodia",
410
- "Myanmar",
411
- "Bahamas",
412
- "Philippines",
413
- "Bangladesh",
414
- "Latvia",
415
- "Vietnam",
416
- "Iceland",
417
- "Azerbaijan",
418
- "Georgia",
419
- "Liberia",
420
- "Kenya",
421
- ]
422
19
  SIZE_RANGE = 2
423
20
 
424
21
 
@@ -1,4 +1,3 @@
1
- import datetime
2
1
  import logging
3
2
  from datetime import date
4
3
  from typing import Optional, Callable, cast
@@ -282,24 +281,6 @@ def compute_price(data: pd.DataFrame) -> pd.DataFrame:
282
281
  return results
283
282
 
284
283
 
285
- def compute_percentile_return_after_rsi_crossover(
286
- data: pd.DataFrame, rsi_threshold: int = 45, period: int = 90
287
- ) -> float:
288
- data_ = cross_value_series(data.RSI, rsi_threshold)
289
- values = []
290
- for crossing_date in data_[data_ == 1].index:
291
- data_crossed = data[
292
- (data.index >= crossing_date)
293
- & (data.index <= crossing_date + datetime.timedelta(days=period))
294
- ]
295
- v = (
296
- data_crossed.CLOSE.pct_change(periods=len(data_crossed.CLOSE) - 1).iloc[-1]
297
- * 100
298
- )
299
- values.append(v)
300
- return float(np.percentile(values, 30))
301
-
302
-
303
284
  def find_last_true_run_start(series: pd.Series) -> Optional[date]:
304
285
  if not series.iloc[-1]:
305
286
  return None
@@ -311,6 +292,16 @@ def find_last_true_run_start(series: pd.Series) -> Optional[date]:
311
292
  return series.index[last_true_run_start].date() # type: ignore
312
293
 
313
294
 
295
+ def sma_50_above_sma_200(data: pd.DataFrame) -> Optional[date]:
296
+ date_1 = find_last_true_run_start(data.SMA_50 > data.SMA_200)
297
+ return date_1
298
+
299
+
300
+ def price_above_sma50(data: pd.DataFrame) -> Optional[date]:
301
+ date_1 = find_last_true_run_start(data.SMA_50 < data.CLOSE)
302
+ return date_1
303
+
304
+
314
305
  def momentum(data: pd.DataFrame) -> Optional[date]:
315
306
  date_1 = find_last_true_run_start(data.SMA_50 < data.CLOSE)
316
307
  date_2 = find_last_true_run_start(data.SMA_200 < data.SMA_50)
@@ -19,8 +19,9 @@ from bullish.analysis.functions import (
19
19
  SMA,
20
20
  ADOSC,
21
21
  PRICE,
22
- compute_percentile_return_after_rsi_crossover,
23
22
  momentum,
23
+ sma_50_above_sma_200,
24
+ price_above_sma50,
24
25
  )
25
26
 
26
27
  logger = logging.getLogger(__name__)
@@ -206,13 +207,6 @@ def indicators_factory() -> List[Indicator]:
206
207
  (d.RSI < 60) & (d.RSI > 40)
207
208
  ].last_valid_index(),
208
209
  ),
209
- Signal(
210
- name="RETURN_AFTER_RSI_CROSSOVER_45_PERIOD_90",
211
- description="Percentile 30 return after RSI crossover 45 in the next 90 days",
212
- type_info="Long",
213
- type=Optional[float],
214
- function=lambda d: compute_percentile_return_after_rsi_crossover(d),
215
- ),
216
210
  ],
217
211
  ),
218
212
  Indicator(
@@ -290,6 +284,20 @@ def indicators_factory() -> List[Indicator]:
290
284
  type=Optional[date],
291
285
  function=lambda d: momentum(d),
292
286
  ),
287
+ Signal(
288
+ name="SMA_50_ABOVE_SMA_200",
289
+ description="SMA 50 is above SMA 200",
290
+ type_info="Overbought",
291
+ type=Optional[date],
292
+ function=lambda d: sma_50_above_sma_200(d),
293
+ ),
294
+ Signal(
295
+ name="PRICE_ABOVE_SMA_50",
296
+ description="Price is above SMA 50",
297
+ type_info="Overbought",
298
+ type=Optional[date],
299
+ function=lambda d: price_above_sma50(d),
300
+ ),
293
301
  ],
294
302
  ),
295
303
  Indicator(
@@ -0,0 +1,201 @@
1
+ import datetime
2
+ from typing import (
3
+ Optional,
4
+ Any,
5
+ Annotated,
6
+ Literal,
7
+ Dict,
8
+ List,
9
+ TYPE_CHECKING,
10
+ get_args,
11
+ )
12
+
13
+ import numpy as np
14
+ import pandas as pd
15
+ from bearish.models.base import Ticker # type: ignore
16
+ from bearish.models.price.prices import Prices # type: ignore
17
+ from bearish.models.query.query import AssetQuery, Symbols # type: ignore
18
+ from pydantic import BaseModel, BeforeValidator, Field, model_validator
19
+
20
+ from bullish.analysis.constants import Industry, IndustryGroup, Sector, Country
21
+
22
+ if TYPE_CHECKING:
23
+ from bullish.database.crud import BullishDb
24
+
25
+ Type = Literal["Mean"]
26
+
27
+ FUNCTIONS = {"Mean": np.mean}
28
+ BASELINE_DATE = datetime.date.today() - datetime.timedelta(days=60)
29
+
30
+
31
+ def compute_normalized_close(close_: pd.Series) -> pd.Series:
32
+ close = close_.copy()
33
+ close.index = close.index.tz_localize(None) # type: ignore
34
+ closest_ts = close.index[
35
+ close.index.get_indexer([BASELINE_DATE], method="nearest")[0]
36
+ ]
37
+ normalized_close = (close / close.loc[closest_ts]).rename("normalized_close")
38
+ normalized_close.index = close_.index
39
+ return normalized_close # type: ignore
40
+
41
+
42
+ def get_industry_comparison_data(
43
+ bullish_db: "BullishDb",
44
+ symbol_data: pd.DataFrame,
45
+ type: Type,
46
+ industry: Industry,
47
+ country: Country,
48
+ ) -> pd.DataFrame:
49
+ try:
50
+ views = bullish_db.read_returns(type, industry, country)
51
+ industry_data = IndustryViews.from_views(views).to_dataframe()
52
+ 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:
56
+ return pd.DataFrame()
57
+
58
+
59
+ class PricesReturns(Prices): # type: ignore
60
+
61
+ def returns(self) -> pd.DataFrame:
62
+ try:
63
+ data = self.to_dataframe()
64
+ data["simple_return"] = data.close.pct_change() * 100
65
+ data["log_return"] = (data.close / data.close.shift(1)).apply(np.log) * 100
66
+ data["normalized_close"] = compute_normalized_close(data.close)
67
+ return data[["simple_return", "log_return", "normalized_close"]] # type: ignore
68
+ except Exception:
69
+ return pd.DataFrame(
70
+ columns=["simple_return", "log_return", "normalized_close"]
71
+ )
72
+
73
+
74
+ def to_float(value: Any) -> Optional[float]:
75
+ if value == "None":
76
+ return None
77
+ if value is None:
78
+ return None
79
+ if isinstance(value, str):
80
+ try:
81
+ return float(value)
82
+ except ValueError:
83
+ return None
84
+ return float(value)
85
+
86
+
87
+ class Basedate(BaseModel):
88
+ date: datetime.date
89
+
90
+
91
+ class KPI(BaseModel):
92
+ simple_return: Annotated[float, BeforeValidator(to_float), Field(None)]
93
+ log_return: Annotated[float, BeforeValidator(to_float), Field(None)]
94
+ normalized_close: Annotated[float, BeforeValidator(to_float), Field(None)]
95
+
96
+
97
+ class BaseIndustryView(Basedate, KPI): ...
98
+
99
+
100
+ class IndustryView(BaseIndustryView):
101
+ created_at: datetime.date
102
+ country: Country
103
+ industry: Industry
104
+ industry_group: Optional[IndustryGroup] = None
105
+ sector: Optional[Sector] = None
106
+ type: Type
107
+
108
+ @model_validator(mode="before")
109
+ def _validate(cls, values: Dict[str, Any]) -> Dict[str, Any]: # noqa: N805
110
+ created_at = datetime.date.today()
111
+ current_date = values.get("date", created_at)
112
+ return (
113
+ {"date": current_date}
114
+ | values
115
+ | {
116
+ "created_at": created_at,
117
+ }
118
+ )
119
+
120
+ @classmethod
121
+ def from_data(
122
+ cls,
123
+ data: pd.DataFrame,
124
+ function_name: Type,
125
+ industry: Industry,
126
+ country: Country,
127
+ ) -> List["IndustryView"]:
128
+ function = FUNCTIONS[function_name]
129
+ data_ = []
130
+ for field in KPI.model_fields:
131
+
132
+ data__ = (
133
+ data[field].apply(function, axis=1).rename(field)
134
+ if data[[field]].shape[1] > 1
135
+ else data[field]
136
+ )
137
+
138
+ data_.append(data__)
139
+
140
+ data_final = pd.concat(data_, axis=1)
141
+ data_final["date"] = data_final.index
142
+ return [
143
+ cls.model_validate(
144
+ r | {"industry": industry, "type": function_name, "country": country}
145
+ )
146
+ for r in data_final.to_dict(orient="records")
147
+ ]
148
+
149
+ @classmethod
150
+ def from_db(
151
+ cls, bullish: "BullishDb", industry: Industry, country: Country
152
+ ) -> List["IndustryView"]:
153
+ returns = []
154
+ symbols = bullish.read_industry_symbols(industries=[industry], country=country)
155
+ query = AssetQuery(
156
+ symbols=Symbols(equities=[Ticker(symbol=s) for s in symbols])
157
+ )
158
+ data = bullish.read_series(query, months=6)
159
+ raw_data = [
160
+ PricesReturns(prices=[d for d in data if d.symbol == s]).returns()
161
+ for s in symbols
162
+ ]
163
+ raw_data = [r for r in raw_data if not r.empty]
164
+
165
+ if raw_data:
166
+ data_ = pd.concat(raw_data, axis=1)
167
+ for function_name in FUNCTIONS:
168
+ returns.extend(cls.from_data(data_, function_name, industry, country)) # type: ignore
169
+ return returns
170
+
171
+
172
+ class IndustryViews(BaseModel):
173
+ views: List[IndustryView]
174
+
175
+ def to_dataframe(self) -> pd.DataFrame:
176
+ data = pd.DataFrame.from_records(
177
+ [
178
+ p.model_dump(include=set(BaseIndustryView.model_fields))
179
+ for p in self.views
180
+ ]
181
+ )
182
+ if data.empty:
183
+ return data
184
+ data = data.set_index("date", inplace=False)
185
+ data = data.sort_index(inplace=False)
186
+
187
+ data.index = pd.to_datetime(data.index, utc=True)
188
+ data = data[~data.index.duplicated(keep="first")]
189
+ return data
190
+
191
+ @classmethod
192
+ def from_views(cls, views: List[IndustryView]) -> "IndustryViews":
193
+ return cls(views=views)
194
+
195
+
196
+ def compute_industry_view(bullish: "BullishDb") -> None:
197
+ for country in get_args(Country):
198
+ for industry in get_args(Industry):
199
+ returns = IndustryView.from_db(bullish, industry, country)
200
+ if returns:
201
+ bullish.write_returns(returns)