bullishpy 0.4.0__py3-none-any.whl → 0.6.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,111 +1,613 @@
1
- from functools import cached_property
2
- from typing import Literal, Set, get_args, Dict, Any, Optional, List
1
+ import datetime
2
+ from datetime import date
3
+ from typing import Literal, get_args, Any, Optional, List, Tuple, Type, Dict
3
4
 
4
5
  from bearish.types import SeriesLength # type: ignore
5
- from pydantic import BaseModel, Field
6
+ from pydantic import BaseModel, Field, ConfigDict
7
+ from pydantic import create_model
8
+ from pydantic.fields import FieldInfo
9
+
10
+ from bullish.analysis.analysis import (
11
+ YearlyFundamentalAnalysis,
12
+ QuarterlyFundamentalAnalysis,
13
+ TechnicalAnalysisModels,
14
+ )
6
15
 
7
16
  Industry = Literal[
8
- "Food & Staples Retailing",
9
- "Packaged Foods",
10
- "Grocery Stores",
11
- "Household Products",
12
- "Household & Personal Products",
13
- "Confectioners",
14
- "Beverages",
15
- "Beverages - Non - Alcoholic",
16
- "Beverages - Wineries & Distilleries",
17
- "Pharmaceuticals",
18
- "Health Care Providers & Services",
19
- "Health Care Equipment & Supplies",
20
- "Healthcare Plans",
21
- "Medical Devices",
22
- "Medical Instruments & Supplies",
23
- "Medical Care Facilities",
17
+ "Publishing",
18
+ "Internet Retail",
19
+ "Scientific & Technical Instruments",
20
+ "Engineering & Construction",
24
21
  "Diagnostics & Research",
25
- "Drug Manufacturers - General",
22
+ "Software - Infrastructure",
23
+ "Thermal Coal",
24
+ "Software - Application",
25
+ "Auto Manufacturers",
26
+ "Farm Products",
27
+ "Medical Devices",
28
+ "Education & Training Services",
29
+ "Auto Parts",
30
+ "Specialty Chemicals",
31
+ "Marine Shipping",
32
+ "Biotechnology",
33
+ "Real Estate Services",
34
+ "Gold",
35
+ "Entertainment",
36
+ "Specialty Retail",
37
+ "Utilities - Independent Power Producers",
38
+ "Steel",
39
+ "Mortgage Finance",
40
+ "Communication Equipment",
26
41
  "Drug Manufacturers - Specialty & Generic",
27
- "Pharmaceutical Retailers",
42
+ "Electronic Gaming & Multimedia",
43
+ "Banks - Regional",
44
+ "Oil & Gas E&P",
45
+ "Travel Services",
46
+ "Real Estate - Diversified",
47
+ "Telecom Services",
48
+ "Uranium",
49
+ "Consulting Services",
50
+ "Waste Management",
51
+ "Agricultural Inputs",
52
+ "Utilities - Diversified",
53
+ "Auto & Truck Dealerships",
54
+ "Confectioners",
55
+ "Other Industrial Metals & Mining",
56
+ "Beverages - Wineries & Distilleries",
57
+ "Oil & Gas Midstream",
58
+ "Recreational Vehicles",
59
+ "Electrical Equipment & Parts",
60
+ "Household & Personal Products",
61
+ "Packaging & Containers",
62
+ "REIT - Specialty",
63
+ "Home Improvement Retail",
64
+ "Electronic Components",
65
+ "Asset Management",
66
+ "Consumer Electronics",
67
+ "Conglomerates",
28
68
  "Health Information Services",
69
+ "Medical Instruments & Supplies",
70
+ "Building Products & Equipment",
71
+ "Information Technology Services",
72
+ "Specialty Industrial Machinery",
73
+ "Food Distribution",
74
+ "Packaged Foods",
75
+ "Rental & Leasing Services",
29
76
  "Medical Distribution",
30
- "Electric Utilities",
31
- "Gas Utilities",
32
- "Water Utilities",
33
- "Utilities - Diversified",
34
- "Utilities - Regulated Electric",
35
- "Utilities - Regulated Gas",
77
+ "Grocery Stores",
78
+ "Advertising Agencies",
79
+ "Beverages - Non - Alcoholic",
80
+ "Apparel Manufacturing",
81
+ "Oil & Gas Equipment & Services",
82
+ "Coking Coal",
83
+ "Industrial Distribution",
84
+ "Restaurants",
85
+ "Beverages - Brewers",
86
+ "Chemicals",
87
+ "Real Estate - Development",
88
+ "Credit Services",
89
+ "Tobacco",
90
+ "Metal Fabrication",
91
+ "Building Materials",
92
+ "Residential Construction",
93
+ "Specialty Business Services",
94
+ "REIT - Hotel & Motel",
95
+ "Internet Content & Information",
96
+ "Lodging",
97
+ "Furnishings, Fixtures & Appliances",
98
+ "Airlines",
99
+ "Computer Hardware",
100
+ "Integrated Freight & Logistics",
101
+ "Solar",
102
+ "Capital Markets",
103
+ "Leisure",
104
+ "Airports & Air Services",
105
+ "Aluminum",
106
+ "Insurance Brokers",
107
+ "Semiconductors",
108
+ "REIT - Retail",
109
+ "Luxury Goods",
110
+ "Lumber & Wood Production",
111
+ "REIT - Mortgage",
112
+ "Semiconductor Equipment & Materials",
113
+ "Aerospace & Defense",
114
+ "Security & Protection Services",
36
115
  "Utilities - Renewable",
37
- "Utilities - Independent Power Producers",
38
- "Waste Management",
116
+ "Utilities - Regulated Gas",
117
+ "Apparel Retail",
39
118
  "Pollution & Treatment Controls",
40
- "Security & Protection Services",
41
- "Insurance",
42
- "Insurance - Property & Casual",
119
+ "Broadcasting",
120
+ "Resorts & Casinos",
121
+ "Other Precious Metals & Mining",
122
+ "Financial Data & Stock Exchanges",
123
+ "Footwear & Accessories",
124
+ "Medical Care Facilities",
125
+ "Electronics & Computer Distribution",
126
+ "Gambling",
127
+ "Tools & Accessories",
128
+ "Insurance - Property & Casualty",
129
+ "Utilities - Regulated Water",
130
+ "Insurance - Specialty",
131
+ "Personal Services",
132
+ "Pharmaceutical Retailers",
133
+ "Farm & Heavy Construction Machinery",
134
+ "Utilities - Regulated Electric",
135
+ "Department Stores",
136
+ "Staffing & Employment Services",
137
+ "Textile Manufacturing",
138
+ "Silver",
139
+ "REIT - Industrial",
140
+ "REIT - Diversified",
141
+ "Copper",
142
+ "Business Equipment & Supplies",
143
+ "Infrastructure Operations",
144
+ "Trucking",
145
+ "Insurance - Reinsurance",
146
+ "Insurance - Diversified",
147
+ "Drug Manufacturers - General",
148
+ "Oil & Gas Drilling",
149
+ "Banks - Diversified",
150
+ "REIT - Residential",
151
+ "Oil & Gas Refining & Marketing",
152
+ "Shell Companies",
153
+ "Financial Conglomerates",
154
+ "Paper & Paper Products",
155
+ "Insurance - Life",
156
+ "REIT - Office",
157
+ "Railroads",
158
+ "Oil & Gas Integrated",
159
+ "Healthcare Plans",
160
+ "REIT - Healthcare Facilities",
161
+ "Discount Stores",
162
+ ]
163
+
164
+ IndustryGroup = Literal[
165
+ "publishing",
166
+ "internet-retail",
167
+ "scientific-technical-instruments",
168
+ "engineering-construction",
169
+ "diagnostics-research",
170
+ "software-infrastructure",
171
+ "thermal-coal",
172
+ "software-application",
173
+ "auto-manufacturers",
174
+ "farm-products",
175
+ "medical-devices",
176
+ "education-training-services",
177
+ "auto-parts",
178
+ "specialty-chemicals",
179
+ "marine-shipping",
180
+ "biotechnology",
181
+ "real-estate-services",
182
+ "gold",
183
+ "entertainment",
184
+ "specialty-retail",
185
+ "utilities-independent-power-producers",
186
+ "steel",
187
+ "mortgage-finance",
188
+ "communication-equipment",
189
+ "drug-manufacturers-specialty-generic",
190
+ "electronic-gaming-multimedia",
191
+ "banks-regional",
192
+ "oil-gas-e-p",
193
+ "travel-services",
194
+ "real-estate-diversified",
195
+ "telecom-services",
196
+ "uranium",
197
+ "consulting-services",
198
+ "waste-management",
199
+ "agricultural-inputs",
200
+ "utilities-diversified",
201
+ "auto-truck-dealerships",
202
+ "confectioners",
203
+ "other-industrial-metals-mining",
204
+ "beverages-wineries-distilleries",
205
+ "oil-gas-midstream",
206
+ "recreational-vehicles",
207
+ "electrical-equipment-parts",
208
+ "household-personal-products",
209
+ "packaging-containers",
210
+ "reit-specialty",
211
+ "home-improvement-retail",
212
+ "electronic-components",
213
+ "asset-management",
214
+ "consumer-electronics",
215
+ "conglomerates",
216
+ "health-information-services",
217
+ "medical-instruments-supplies",
218
+ "building-products-equipment",
219
+ "information-technology-services",
220
+ "specialty-industrial-machinery",
221
+ "food-distribution",
222
+ "packaged-foods",
223
+ "rental-leasing-services",
224
+ "medical-distribution",
225
+ "grocery-stores",
226
+ "advertising-agencies",
227
+ "beverages-non-alcoholic",
228
+ "apparel-manufacturing",
229
+ "oil-gas-equipment-services",
230
+ "coking-coal",
231
+ "industrial-distribution",
232
+ "restaurants",
233
+ "beverages-brewers",
234
+ "chemicals",
235
+ "real-estate-development",
236
+ "credit-services",
237
+ "tobacco",
238
+ "metal-fabrication",
239
+ "building-materials",
240
+ "residential-construction",
241
+ "specialty-business-services",
242
+ "reit-hotel-motel",
243
+ "internet-content-information",
244
+ "lodging",
245
+ "furnishings-fixtures-appliances",
246
+ "airlines",
247
+ "computer-hardware",
248
+ "integrated-freight-logistics",
249
+ "solar",
250
+ "capital-markets",
251
+ "leisure",
252
+ "airports-air-services",
253
+ "aluminum",
254
+ "insurance-brokers",
255
+ "semiconductors",
256
+ "reit-retail",
257
+ "luxury-goods",
258
+ "lumber-wood-production",
259
+ "reit-mortgage",
260
+ "semiconductor-equipment-materials",
261
+ "aerospace-defense",
262
+ "security-protection-services",
263
+ "utilities-renewable",
264
+ "utilities-regulated-gas",
265
+ "apparel-retail",
266
+ "pollution-treatment-controls",
267
+ "broadcasting",
268
+ "resorts-casinos",
269
+ "other-precious-metals-mining",
270
+ "financial-data-stock-exchanges",
271
+ "footwear-accessories",
272
+ "medical-care-facilities",
273
+ "electronics-computer-distribution",
274
+ "gambling",
275
+ "tools-accessories",
276
+ "insurance-property-casualty",
277
+ "utilities-regulated-water",
278
+ "insurance-specialty",
279
+ "personal-services",
280
+ "pharmaceutical-retailers",
281
+ "farm-heavy-construction-machinery",
282
+ "utilities-regulated-electric",
283
+ "department-stores",
284
+ "staffing-employment-services",
285
+ "textile-manufacturing",
286
+ "silver",
287
+ "reit-industrial",
288
+ "reit-diversified",
289
+ "copper",
290
+ "business-equipment-supplies",
291
+ "infrastructure-operations",
292
+ "trucking",
293
+ "insurance-reinsurance",
294
+ "insurance-diversified",
295
+ "drug-manufacturers-general",
296
+ "oil-gas-drilling",
297
+ "banks-diversified",
298
+ "reit-residential",
299
+ "oil-gas-refining-marketing",
300
+ "shell-companies",
301
+ "financial-conglomerates",
302
+ "paper-paper-products",
303
+ "insurance-life",
304
+ "reit-office",
305
+ "railroads",
306
+ "oil-gas-integrated",
307
+ "healthcare-plans",
308
+ "reit-healthcare-facilities",
309
+ "discount-stores",
310
+ ]
311
+
312
+ Sector = Literal[
313
+ "Communication Services",
314
+ "Consumer Cyclical",
315
+ "Technology",
316
+ "Industrials",
317
+ "Healthcare",
318
+ "Energy",
319
+ "Consumer Defensive",
320
+ "Basic Materials",
321
+ "Real Estate",
322
+ "Utilities",
323
+ "Financial Services",
324
+ "Conglomerates",
43
325
  ]
44
326
 
45
327
  Country = Literal[
328
+ "Australia",
329
+ "China",
330
+ "Japan",
331
+ "United kingdom",
332
+ "United states",
333
+ "Poland",
334
+ "Switzerland",
335
+ "Canada",
336
+ "Greece",
337
+ "Spain",
46
338
  "Germany",
339
+ "Indonesia",
340
+ "Belgium",
47
341
  "France",
48
342
  "Netherlands",
49
- "Belgium",
343
+ "British virgin islands",
50
344
  "Italy",
51
- "Spain",
52
- "Switzerland",
345
+ "Hungary",
346
+ "Austria",
347
+ "Finland",
53
348
  "Sweden",
54
- "Denmark",
349
+ "Bermuda",
350
+ "Taiwan",
351
+ "Israel",
352
+ "Ukraine",
353
+ "Singapore",
354
+ "Jersey",
355
+ "Ireland",
356
+ "Luxembourg",
357
+ "Cyprus",
358
+ "Cayman islands",
55
359
  "Norway",
56
- "Finland",
360
+ "Denmark",
361
+ "Hong kong",
362
+ "New zealand",
363
+ "Kazakhstan",
364
+ "Nigeria",
365
+ "Argentina",
366
+ "Brazil",
367
+ "Czech republic",
368
+ "Mauritius",
369
+ "South africa",
370
+ "India",
371
+ "Mexico",
372
+ "Mongolia",
373
+ "Slovenia",
374
+ "Thailand",
375
+ "Malaysia",
376
+ "Costa rica",
377
+ "Isle of man",
378
+ "Egypt",
379
+ "Turkey",
380
+ "United arab emirates",
381
+ "Colombia",
382
+ "Gibraltar",
383
+ "Malta",
384
+ "Liechtenstein",
385
+ "Guernsey",
386
+ "Peru",
387
+ "Estonia",
388
+ "French guiana",
57
389
  "Portugal",
58
- "Austria",
59
- "United states",
390
+ "Uruguay",
391
+ "Chile",
392
+ "Martinique",
393
+ "Monaco",
394
+ "Panama",
395
+ "Papua new guinea",
396
+ "South korea",
397
+ "Macau",
398
+ "Gabon",
399
+ "Romania",
400
+ "Senegal",
401
+ "Morocco",
402
+ "Jordan",
403
+ "Lithuania",
404
+ "Dominican republic",
405
+ "Reunion",
406
+ "Zambia",
407
+ "Cambodia",
408
+ "Myanmar",
409
+ "Bahamas",
410
+ "Philippines",
411
+ "Bangladesh",
412
+ "Latvia",
413
+ "Vietnam",
414
+ "Iceland",
415
+ "Azerbaijan",
416
+ "Georgia",
417
+ "Liberia",
418
+ "Kenya",
60
419
  ]
61
- SIGNS = {
62
- "price_per_earning_ratio": "<",
63
- "market_capitalization": ">",
64
- "industry": " IN ",
65
- "country": " IN ",
66
- }
420
+ SIZE_RANGE = 2
67
421
 
68
422
 
69
- class FilterQuery(BaseModel):
70
- positive_free_cash_flow: bool = Field(
71
- False, description="The username for the database."
72
- )
73
- positive_net_income: bool = False
74
- positive_operating_income: bool = False
75
- quarterly_positive_free_cash_flow: bool = False
76
- quarterly_positive_net_income: bool = False
77
- quarterly_positive_operating_income: bool = False
78
- growing_net_income: bool = False
79
- quarterly_operating_cash_flow_is_higher_than_net_income: bool = False
80
- operating_cash_flow_is_higher_than_net_income: bool = False
81
- rsi_last_value_exists: bool = False
82
- market_capitalization: int = Field(
83
- 0, ge=0, multiple_of=1000, description="Positive integer with step count of 10."
84
- )
85
- price_per_earning_ratio: int = Field(
86
- 0, ge=0, multiple_of=10, description="Positive integer with step count of 10."
423
+ def _get_type(name: str, info: FieldInfo) -> Tuple[Any, Any]:
424
+ alias = info.alias or " ".join(name.capitalize().split("_")).strip()
425
+ if info.annotation == Optional[float]: # type: ignore
426
+ ge = next((item.ge for item in info.metadata if hasattr(item, "ge")), 0)
427
+ le = next((item.le for item in info.metadata if hasattr(item, "le")), 100)
428
+ default = [ge, le]
429
+ return (
430
+ Optional[List[float]],
431
+ Field(default=default, alias=alias, description=info.description),
432
+ )
433
+ elif info.annotation == Optional[date]: # type: ignore
434
+ le = date.today()
435
+ ge = le - datetime.timedelta(days=30 * 2) # 30 days * 12 months
436
+ return (
437
+ List[date],
438
+ Field(default=[ge, le], alias=alias, description=info.description),
439
+ )
440
+ else:
441
+ raise NotImplementedError
442
+
443
+
444
+ FUNDAMENTAL_ANALYSIS_GROUP = ["income", "cash_flow", "eps"]
445
+
446
+
447
+ def _get_fundamental_analysis_boolean_fields() -> List[str]:
448
+ return [
449
+ name
450
+ for name, info in {
451
+ **YearlyFundamentalAnalysis.model_fields,
452
+ **QuarterlyFundamentalAnalysis.model_fields,
453
+ }.items()
454
+ if info.annotation == Optional[bool]
455
+ ]
456
+
457
+
458
+ def get_boolean_field_group(group: str) -> List[str]:
459
+ groups = FUNDAMENTAL_ANALYSIS_GROUP.copy()
460
+ groups.remove(group)
461
+ return [
462
+ name
463
+ for name in _get_fundamental_analysis_boolean_fields()
464
+ if group in name and not any(g in name for g in groups)
465
+ ]
466
+
467
+
468
+ INCOME_GROUP = get_boolean_field_group("income")
469
+ CASH_FLOW_GROUP = get_boolean_field_group("cash_flow")
470
+ EPS_GROUP = get_boolean_field_group("eps")
471
+ PROPERTIES_GROUP = list(
472
+ set(_get_fundamental_analysis_boolean_fields()).difference(
473
+ {*INCOME_GROUP, *CASH_FLOW_GROUP, *EPS_GROUP}
87
474
  )
88
- industry: Set[Industry] = Field(None, description="Industry name.") # type: ignore
89
- country: Set[Country] = Field(None, description="Country name.") # type: ignore
475
+ )
476
+
477
+ GROUP_MAPPING: Dict[str, List[str]] = {
478
+ "income": INCOME_GROUP,
479
+ "cash_flow": CASH_FLOW_GROUP,
480
+ "eps": EPS_GROUP,
481
+ "properties": PROPERTIES_GROUP,
482
+ "country": list(get_args(Country)),
483
+ "industry": list(get_args(Industry)),
484
+ "industry_group": list(get_args(IndustryGroup)),
485
+ "sector": list(get_args(Sector)),
486
+ "symbol": [],
487
+ }
90
488
 
91
- @cached_property
92
- def query_parameters(self) -> Dict[str, Any]:
93
- if not bool(self.industry):
94
- self.industry = tuple(get_args(Industry)) # type: ignore
95
- if not bool(self.country):
96
- self.country = tuple(get_args(Country)) # type: ignore
97
- return self.model_dump(exclude_defaults=True, exclude_unset=True)
98
489
 
99
- def to_query(self) -> str:
100
- query = " AND ".join(
101
- [f"{k}{SIGNS.get(k,'=')}{v}" for k, v in self.query_parameters.items()]
490
+ def _create_fundamental_analysis_models() -> List[Type[BaseModel]]:
491
+ models = []
492
+ boolean_fields = {
493
+ "income": (Optional[List[str]], Field(default=None, description="Income")),
494
+ "cash_flow": (
495
+ Optional[List[str]],
496
+ Field(default=None, description="Cash flow"),
497
+ ),
498
+ "eps": (
499
+ Optional[List[str]],
500
+ Field(default=None, description="Earnings per share"),
501
+ ),
502
+ "properties": (
503
+ Optional[List[str]],
504
+ Field(default=None, description="General properties"),
505
+ ),
506
+ }
507
+ yearly_fields = {
508
+ name: _get_type(name, info)
509
+ for name, info in YearlyFundamentalAnalysis.model_fields.items()
510
+ if info.annotation != Optional[bool] # type: ignore
511
+ }
512
+ quarterly_fields = {
513
+ name: _get_type(name, info)
514
+ for name, info in QuarterlyFundamentalAnalysis.model_fields.items()
515
+ if info.annotation != Optional[bool]
516
+ }
517
+ for property in [
518
+ (boolean_fields, "Selection filter", "SelectionFilter"),
519
+ (yearly_fields, "Yearly properties", "YearlyFilter"),
520
+ (quarterly_fields, "Quarterly properties", "QuarterlyFilter"),
521
+ ]:
522
+ model_ = create_model( # type: ignore
523
+ property[-1],
524
+ __config__=ConfigDict(populate_by_name=True),
525
+ **property[0],
102
526
  )
103
- return query
527
+ model_._description = property[1]
528
+ models.append(model_)
529
+
530
+ return models
531
+
532
+
533
+ def create_technical_analysis_models() -> List[Type[BaseModel]]:
534
+ models = []
535
+ for model in TechnicalAnalysisModels:
536
+ model_ = create_model( # type: ignore
537
+ f"{model.__name__}Filter", # type: ignore
538
+ __config__=ConfigDict(populate_by_name=True),
539
+ **{
540
+ name: _get_type(name, info) for name, info in model.model_fields.items() # type: ignore
541
+ },
542
+ )
543
+
544
+ model_._description = model._description # type: ignore
545
+ models.append(model_)
546
+ return models
547
+
548
+
549
+ TechnicalAnalysisFilters = create_technical_analysis_models()
550
+ FundamentalAnalysisFilters = _create_fundamental_analysis_models()
551
+
552
+
553
+ class GeneralFilter(BaseModel):
554
+ country: Optional[List[str]] = None
555
+ industry: Optional[List[str]] = None
556
+ industry_group: Optional[List[str]] = None
557
+ sector: Optional[List[str]] = None
558
+ symbol: Optional[List[str]] = None
559
+ market_capitalization: Optional[List[float]] = Field(default=[5e8, 1e12])
560
+
561
+
562
+ class FilterQuery(GeneralFilter, *TechnicalAnalysisFilters, *FundamentalAnalysisFilters): # type: ignore
563
+
564
+ def valid(self) -> bool:
565
+ return any(
566
+ bool(v)
567
+ for _, v in self.model_dump(
568
+ exclude_defaults=True, exclude_unset=True
569
+ ).items()
570
+ )
571
+
572
+ def to_query(self) -> str:
573
+ parameters = self.model_dump(exclude_defaults=True, exclude_unset=True)
574
+ query = []
575
+ for parameter, value in parameters.items():
576
+ if not value:
577
+ continue
578
+
579
+ if (
580
+ isinstance(value, list)
581
+ and all(isinstance(item, str) for item in value)
582
+ and parameter not in GeneralFilter.model_fields
583
+ ):
584
+ query.append(" AND ".join([f"{v}=1" for v in value]))
585
+ elif (
586
+ isinstance(value, list)
587
+ and len(value) == SIZE_RANGE
588
+ and all(isinstance(item, (int, float)) for item in value)
589
+ ):
590
+ query.append(f"{parameter} BETWEEN {value[0]} AND {value[1]}")
591
+ elif (
592
+ isinstance(value, list)
593
+ and len(value) == SIZE_RANGE
594
+ and all(isinstance(item, date) for item in value)
595
+ ):
596
+ query.append(f"{parameter} BETWEEN '{value[0]}' AND '{value[1]}'")
597
+ elif (
598
+ isinstance(value, list)
599
+ and all(isinstance(item, str) for item in value)
600
+ and parameter in GeneralFilter.model_fields
601
+ ):
602
+ general_filters = [f"'{v}'" for v in value]
603
+ query.append(f"{parameter} IN ({', '.join(general_filters)})")
604
+ else:
605
+ raise NotImplementedError
606
+ query_ = " AND ".join(query)
607
+ return query_
104
608
 
105
609
 
106
- class FilterQueryStored(FilterQuery):
107
- industry: Optional[List[Industry]] = None # type: ignore
108
- country: Optional[List[Country]] = None # type: ignore
610
+ class FilterQueryStored(FilterQuery): ...
109
611
 
110
612
 
111
613
  class FilterUpdate(BaseModel):