Skip to content

Commit c429ff7

Browse files
committed
stock screener
1 parent cf0cd64 commit c429ff7

File tree

9 files changed

+159
-13
lines changed

9 files changed

+159
-13
lines changed

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
name='tigeropen',
1717
version=__VERSION__,
1818
description='TigerBrokers Open API',
19+
long_description = file: README.md
20+
long_description_content_type = text/markdown
1921
packages=find_packages(exclude=[]),
2022
author='TigerBrokers',
2123
author_email='[email protected]',

tigeropen/common/consts/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,10 @@ class IndustryLevel(Enum):
140140
GGROUP = 'GGROUP'
141141
GIND = 'GIND'
142142
GSUBIND = 'GSUBIND'
143+
144+
145+
@unique
146+
class SortDirection(Enum):
147+
ASC = 'ASC'
148+
DESC = 'DESC'
149+

tigeropen/common/consts/fundamental_fields.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# -*- coding: utf-8 -*-
22

3-
from enum import Enum, unique
3+
from enum import unique
4+
5+
from tigeropen.common.model import Field
46

57

68
@unique
7-
class Valuation(Enum):
9+
class Valuation(Field):
810
"""
911
估值指标
1012
tev: total enterprice value
@@ -30,7 +32,7 @@ class Valuation(Enum):
3032

3133

3234
@unique
33-
class Income(Enum):
35+
class Income(Field):
3436
"""
3537
利润表
3638
"""
@@ -187,7 +189,7 @@ class Income(Enum):
187189

188190

189191
@unique
190-
class Balance(Enum):
192+
class Balance(Field):
191193
"""
192194
资产负债表
193195
"""
@@ -316,7 +318,7 @@ class Balance(Enum):
316318

317319

318320
@unique
319-
class CashFlow(Enum):
321+
class CashFlow(Field):
320322
"""
321323
现金流量表
322324
"""
@@ -405,7 +407,7 @@ class CashFlow(Enum):
405407

406408

407409
@unique
408-
class BalanceSheetRatio(Enum):
410+
class BalanceSheetRatio(Field):
409411
"""
410412
资产负债表相关比率
411413
"""
@@ -422,7 +424,7 @@ class BalanceSheetRatio(Enum):
422424

423425

424426
@unique
425-
class Growth(Enum):
427+
class Growth(Field):
426428
"""
427429
成长能力
428430
"""
@@ -549,7 +551,7 @@ class Growth(Enum):
549551

550552

551553
@unique
552-
class Leverage(Enum):
554+
class Leverage(Field):
553555
"""
554556
财务杠杆
555557
"""
@@ -571,7 +573,7 @@ class Leverage(Enum):
571573

572574

573575
@unique
574-
class Profitability(Enum):
576+
class Profitability(Field):
575577
"""
576578
盈利能力
577579
"""
@@ -590,4 +592,3 @@ class Profitability(Enum):
590592
levered_free_cash_flow_margin = "levered_free_cash_flow_margin"
591593
unlevered_free_cash_flow_margin = "unlevered_free_cash_flow_margin"
592594
normalized_net_income_margin = "normalized_net_income_margin"
593-

tigeropen/common/consts/service_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
QUOTE_STOCK_TRADE = "quote_stock_trade"
4545
QUOTE_DEPTH = "quote_depth" # level2 深度行情
4646
GRAB_QUOTE_PERMISSION = "grab_quote_permission" # 抢占行情
47+
STOCK_SCREENER = "stock_screener" # 选股器
4748

4849
# 期权行情
4950
OPTION_EXPIRATION = "option_expiration"

tigeropen/common/model.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#
33
# @Date : 2021/11/12
44
# @Author : sukai
5+
from enum import Enum
6+
57

68
class BaseParams:
79
def __init__(self):
@@ -13,4 +15,8 @@ def version(self):
1315

1416
@version.setter
1517
def version(self, value):
16-
self._version = value
18+
self._version = value
19+
20+
21+
class Field(Enum):
22+
pass

tigeropen/quote/domain/filter.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#
33
# @Date : 2021/11/15
44
# @Author : sukai
5+
from tigeropen.common.consts import SortDirection
6+
from tigeropen.common.model import Field
57

68
GREEKS = ['delta', 'gamma', 'theta', 'vega', 'rho']
79

@@ -56,3 +58,29 @@ def _get_greeks(self):
5658

5759
def _min_max(self, k):
5860
return {'min': getattr(self, k + '_min'), 'max': getattr(self, k + '_max')}
61+
62+
63+
class StockFilter:
64+
def __init__(self, field, min_value=None, max_value=None, sort=None, enable=True):
65+
"""
66+
stock filter
67+
:param field: filter field. subclass of tigeropen.common.consts.Field, or field name string
68+
:param min_value:
69+
:param max_value:
70+
:param sort: tigeropen.common.consts.SortDirection. ASC or DESC
71+
:param enable:
72+
"""
73+
self.field = field
74+
self.min_value = min_value
75+
self.max_value = max_value
76+
self.sort = sort
77+
self.enable = enable
78+
79+
def to_dict(self):
80+
return {'field': self.field.name if isinstance(self.field, Field) else self.field,
81+
'min_value': self.min_value,
82+
'max_value': self.max_value,
83+
'sort_direction': self.sort.value if isinstance(self.sort, SortDirection) else self.sort,
84+
}
85+
86+

tigeropen/quote/quote_client.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from tigeropen.common.consts import Market, Language, QuoteRight, BarPeriod, OPEN_API_SERVICE_VERSION_V3
1414
from tigeropen.common.consts import THREAD_LOCAL, SecurityType, CorporateActionType, IndustryLevel
15-
from tigeropen.common.consts.service_types import GRAB_QUOTE_PERMISSION
15+
from tigeropen.common.consts.service_types import GRAB_QUOTE_PERMISSION, STOCK_SCREENER
1616
from tigeropen.common.consts.service_types import MARKET_STATE, ALL_SYMBOLS, ALL_SYMBOL_NAMES, BRIEF, \
1717
TIMELINE, KLINE, TRADE_TICK, OPTION_EXPIRATION, OPTION_CHAIN, FUTURE_EXCHANGE, OPTION_BRIEF, \
1818
OPTION_KLINE, OPTION_TRADE_TICK, FUTURE_KLINE, FUTURE_TICK, FUTURE_CONTRACT_BY_EXCHANGE_CODE, \
@@ -35,7 +35,7 @@
3535
from tigeropen.quote.request import OpenApiRequest
3636
from tigeropen.quote.request.model import MarketParams, MultipleQuoteParams, MultipleContractParams, \
3737
FutureQuoteParams, FutureExchangeParams, FutureTypeParams, FutureTradingTimeParams, SingleContractParams, \
38-
SingleOptionQuoteParams, DepthQuoteParams, OptionChainParams
38+
SingleOptionQuoteParams, DepthQuoteParams, OptionChainParams, StockScreenerParams
3939
from tigeropen.quote.response.future_briefs_response import FutureBriefsResponse
4040
from tigeropen.quote.response.future_contract_response import FutureContractResponse
4141
from tigeropen.quote.response.future_exchange_response import FutureExchangeResponse
@@ -54,6 +54,7 @@
5454
from tigeropen.quote.response.quote_grab_permission_response import QuoteGrabPermissionResponse
5555
from tigeropen.quote.response.quote_ticks_response import TradeTickResponse
5656
from tigeropen.quote.response.quote_timeline_response import QuoteTimelineResponse
57+
from tigeropen.quote.response.screened_stocks_response import ScreenedStocksResponse
5758
from tigeropen.quote.response.stock_briefs_response import StockBriefsResponse
5859
from tigeropen.quote.response.stock_details_response import StockDetailsResponse
5960
from tigeropen.quote.response.stock_short_interest_response import ShortInterestResponse
@@ -1139,6 +1140,31 @@ def get_stock_industry(self, symbol, market=Market.US):
11391140
else:
11401141
raise ApiException(response.code, response.message)
11411142

1143+
def get_screened_stocks(self, market=Market.US, stock_filters=None, page=None, limit=None):
1144+
"""
1145+
screen stocks
1146+
:param market: tigeropen.common.consts.Market
1147+
:param stock_filters: tigeropen.quote.domain.filter.StockFilter or list of StockFilter
1148+
:param page: page begin number
1149+
:param limit: page size limit
1150+
:return:
1151+
"""
1152+
params = StockScreenerParams()
1153+
params.market = market.value
1154+
if stock_filters is not None:
1155+
params.stock_filters = stock_filters if isinstance(stock_filters, list) else [stock_filters]
1156+
params.page = page
1157+
params.limit = limit
1158+
request = OpenApiRequest(STOCK_SCREENER, biz_model=params)
1159+
response_content = self.__fetch_data(request)
1160+
if response_content:
1161+
response = ScreenedStocksResponse()
1162+
response.parse_response_content(response_content)
1163+
if response.is_success():
1164+
return response.stocks
1165+
else:
1166+
raise ApiException(response.code, response.message)
1167+
11421168
def grab_quote_permission(self):
11431169
"""
11441170
抢占行情权限

tigeropen/quote/request/model.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,3 +741,59 @@ def to_openapi_dict(self):
741741
if self.option_filter:
742742
params['option_filter'] = self.option_filter.to_dict()
743743
return params
744+
745+
746+
class StockScreenerParams(BaseParams):
747+
def __init__(self):
748+
super(StockScreenerParams, self).__init__()
749+
self._market = None
750+
self._stock_filters = None
751+
self._page = None
752+
self._limit = None
753+
754+
@property
755+
def market(self):
756+
return self._market
757+
758+
@market.setter
759+
def market(self, value):
760+
self._market = value
761+
762+
@property
763+
def stock_filters(self):
764+
return self._stock_filters
765+
766+
@stock_filters.setter
767+
def stock_filters(self, value):
768+
self._stock_filters = value
769+
770+
@property
771+
def page(self):
772+
return self._page
773+
774+
@page.setter
775+
def page(self, value):
776+
self._page = value
777+
778+
@property
779+
def limit(self):
780+
return self._limit
781+
782+
@limit.setter
783+
def limit(self, value):
784+
self._limit = value
785+
786+
def to_openapi_dict(self):
787+
params = dict()
788+
if self.market:
789+
params['market'] = self.market
790+
if self.stock_filters:
791+
params['filters'] = list()
792+
for item in self.stock_filters:
793+
if item.enable:
794+
params['filters'].append(item.to_dict())
795+
if self.page:
796+
params['page'] = self.page
797+
if self.limit:
798+
params['limit'] = self.limit
799+
return params
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# @Date : 2021/11/18
4+
# @Author : sukai
5+
from tigeropen.common.response import TigerResponse
6+
7+
8+
class ScreenedStocksResponse(TigerResponse):
9+
def __init__(self):
10+
super(ScreenedStocksResponse, self).__init__()
11+
self.stocks = None
12+
self._is_success = None
13+
14+
def parse_response_content(self, response_content):
15+
response = super(ScreenedStocksResponse, self).parse_response_content(response_content)
16+
if 'is_success' in response:
17+
self._is_success = response['is_success']
18+
if self.data and isinstance(self.data, list):
19+
stocks = []

0 commit comments

Comments
 (0)