unique_stock_ticker 0.0.1__tar.gz
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.
- unique_stock_ticker-0.0.1/CHANGELOG.md +9 -0
- unique_stock_ticker-0.0.1/LICENSE +1 -0
- unique_stock_ticker-0.0.1/PKG-INFO +35 -0
- unique_stock_ticker-0.0.1/README.md +3 -0
- unique_stock_ticker-0.0.1/pyproject.toml +44 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/__init__.py +54 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/__init__.py +17 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/client.py +151 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/exception.py +31 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/get_creds.sh +110 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/http_adapter.py +47 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/__init__.py +61 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/base/__init__.py +7 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/base/model.py +10 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/base/request.py +17 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/base/response.py +94 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/entity.py +28 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/instrument.py +132 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/language.py +8 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/listing.py +33 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/lookup.py +8 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/market.py +25 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/price.py +7 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/common/security.py +18 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/end_of_day_history/__init__.py +15 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/end_of_day_history/request.py +20 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/end_of_day_history/response.py +215 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/entity_base/__init__.py +11 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/entity_base/listing/request.py +14 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/entity_base/listing/response.py +93 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/__init__.py +27 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/entities/__init__.py +5 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/entities/request.py +7 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/entities/response.py +94 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/instruments/__init__.py +5 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/instruments/request.py +10 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/instruments/response.py +287 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/markets/__init__.py +5 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/markets/request.py +10 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/free_text_search/markets/response.py +103 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_history/__init__.py +11 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_history/summary/request.py +27 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_history/summary/response.py +96 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_snapshot/__init__.py +15 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_snapshot/quality_of_service.py +8 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_snapshot/request.py +17 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/schema/intraday_snapshot/response.py +187 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/six/settings.py +72 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/clients/utils.py +49 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/config.py +96 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/__init__.py +19 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/config.py +21 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/memory.py +89 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/prompt.py +37 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/schema.py +31 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/service.py +139 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/tests/test_memory.py +285 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/tests/test_prompt.py +20 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/detection/tests/test_service.py +173 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/integrated.py +77 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/__init__.py +29 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/__init__.py +35 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/base/__init__.py +25 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/base/base.py +46 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/base/schema.py +75 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/next.py +52 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/plotly/__init__.py +13 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/plotly/functions.py +146 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/plotly/plotly.py +142 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/plotly/save.py +124 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/backend/utils.py +37 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/config.py +19 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/entity.py +44 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/history.py +173 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/retrieve_and_plot.py +385 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/snapshot.py +88 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/plot/ticker.py +98 -0
- unique_stock_ticker-0.0.1/unique_stock_ticker/stock_ticker_postprocessor.py +54 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.0.1] - 2025-08-18
|
|
9
|
+
- Initial release of `stock_ticker`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
`unique_toolkit` is covered by the [`Unique License v1`](https://github.com/Unique-AG/license/releases/tag/unique-license.v1), unless the/a header or a nested LICENSE specifies another license.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unique_stock_ticker
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary:
|
|
5
|
+
License: Proprietary
|
|
6
|
+
Author: Martin Fadler
|
|
7
|
+
Author-email: martin.fadler@unique.ch
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: Other/Proprietary License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Requires-Dist: numerize (>=0.12,<0.13)
|
|
14
|
+
Requires-Dist: plotly (>=6.3.0,<7.0.0)
|
|
15
|
+
Requires-Dist: pydantic (>=2.8.2,<3.0.0)
|
|
16
|
+
Requires-Dist: pydantic-settings (>=2.10.1,<3.0.0)
|
|
17
|
+
Requires-Dist: pytest (>=8.4.1,<9.0.0)
|
|
18
|
+
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
19
|
+
Requires-Dist: typing-extensions (>=4.9.0,<5.0.0)
|
|
20
|
+
Requires-Dist: unique-sdk (>=0.10.0,<0.11.0)
|
|
21
|
+
Requires-Dist: unique-toolkit (>=0.8.12,<0.9.0)
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Unique stock ticker
|
|
25
|
+
|
|
26
|
+
stock ticker post processor.
|
|
27
|
+
# Changelog
|
|
28
|
+
|
|
29
|
+
All notable changes to this project will be documented in this file.
|
|
30
|
+
|
|
31
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
32
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
33
|
+
|
|
34
|
+
## [0.0.1] - 2025-08-18
|
|
35
|
+
- Initial release of `stock_ticker`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "unique_stock_ticker"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = [
|
|
6
|
+
"Martin Fadler <martin.fadler@unique.ch>",
|
|
7
|
+
"Sadique Sheik <sadique@unique.ch>",
|
|
8
|
+
"Fabian Schläpfer <fabian@unique.ch>",
|
|
9
|
+
"Pascal Hauri <pascal@unique.ch>",
|
|
10
|
+
]
|
|
11
|
+
readme = ["README.md", "CHANGELOG.md"]
|
|
12
|
+
license = "Proprietary"
|
|
13
|
+
|
|
14
|
+
[tool.poetry.dependencies]
|
|
15
|
+
python = "^3.11"
|
|
16
|
+
typing-extensions = "^4.9.0"
|
|
17
|
+
pydantic = "^2.8.2"
|
|
18
|
+
pydantic-settings = "^2.10.1"
|
|
19
|
+
python-dotenv = "^1.0.1"
|
|
20
|
+
unique-sdk = "^0.10.0"
|
|
21
|
+
unique-toolkit = "^0.8.12"
|
|
22
|
+
plotly = "^6.3.0"
|
|
23
|
+
numerize = "^0.12"
|
|
24
|
+
pytest = "^8.4.1"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[tool.poetry.group.dev.dependencies]
|
|
28
|
+
python = "^3.11"
|
|
29
|
+
typing-extensions = "^4.9.0"
|
|
30
|
+
pydantic = "^2.8.2"
|
|
31
|
+
pydantic-settings = "^2.10.1"
|
|
32
|
+
unique-sdk = { path = "../unique_sdk" }
|
|
33
|
+
unique-toolkit = { path = "../unique_toolkit" }
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["poetry-core"]
|
|
38
|
+
build-backend = "poetry.core.masonry.api"
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
target-version = "py311"
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
extend-select = ["I"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from unique_toolkit.stock_ticker.config import StockTickerConfig
|
|
2
|
+
from unique_toolkit.stock_ticker.detection.config import StockTickerDetectionConfig
|
|
3
|
+
from unique_toolkit.stock_ticker.detection.memory import (
|
|
4
|
+
StockTickerMemoryConfig,
|
|
5
|
+
StockTickerMemoryManager,
|
|
6
|
+
StockTickerMemorySchema,
|
|
7
|
+
)
|
|
8
|
+
from unique_toolkit.stock_ticker.detection.schema import (
|
|
9
|
+
StockTicker,
|
|
10
|
+
getStockTickersResponse,
|
|
11
|
+
)
|
|
12
|
+
from unique_toolkit.stock_ticker.detection.service import (
|
|
13
|
+
StockTickerService,
|
|
14
|
+
get_stock_ticker_service,
|
|
15
|
+
)
|
|
16
|
+
from unique_toolkit.stock_ticker.integrated import (
|
|
17
|
+
retrieve_tickers_and_plot_history,
|
|
18
|
+
)
|
|
19
|
+
from unique_toolkit.stock_ticker.plot.backend import (
|
|
20
|
+
NextPlottingBackend,
|
|
21
|
+
NextTickerPlotConfig,
|
|
22
|
+
PlotlyPlottingBackend,
|
|
23
|
+
PlotlyTickerPlotConfig,
|
|
24
|
+
PlottingBackend,
|
|
25
|
+
PlottingBackendName,
|
|
26
|
+
StockTickerDataRetrievalConfig,
|
|
27
|
+
find_and_plot_history_for_tickers,
|
|
28
|
+
find_history_for_tickers,
|
|
29
|
+
get_plotting_backend,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"StockTickerDetectionConfig",
|
|
34
|
+
"StockTickerMemoryManager",
|
|
35
|
+
"StockTickerMemorySchema",
|
|
36
|
+
"StockTickerMemoryConfig",
|
|
37
|
+
"getStockTickersResponse",
|
|
38
|
+
"StockTickerService",
|
|
39
|
+
"StockTicker",
|
|
40
|
+
"StockTickerDataRetrievalConfig",
|
|
41
|
+
"find_and_plot_history_for_tickers",
|
|
42
|
+
"find_history_for_tickers",
|
|
43
|
+
"NextPlottingBackend",
|
|
44
|
+
"PlotlyPlottingBackend",
|
|
45
|
+
"PlottingBackend",
|
|
46
|
+
"PlottingBackendName",
|
|
47
|
+
"PlotlyTickerPlotConfig",
|
|
48
|
+
"NextTickerPlotConfig",
|
|
49
|
+
"get_stock_ticker_service",
|
|
50
|
+
"retrieve_tickers_and_plot_history",
|
|
51
|
+
"StockTickerDetectionConfig",
|
|
52
|
+
"get_plotting_backend",
|
|
53
|
+
"StockTickerConfig",
|
|
54
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from unique_stock_ticker.clients.six.client import (
|
|
2
|
+
SixApiClient,
|
|
3
|
+
get_six_api_client,
|
|
4
|
+
)
|
|
5
|
+
from unique_stock_ticker.clients.six.exception import (
|
|
6
|
+
NoCredentialsException,
|
|
7
|
+
SixApiException,
|
|
8
|
+
raise_errors_from_api_response,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SixApiClient",
|
|
13
|
+
"get_six_api_client",
|
|
14
|
+
"SixApiException",
|
|
15
|
+
"raise_errors_from_api_response",
|
|
16
|
+
"NoCredentialsException",
|
|
17
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import urllib.parse
|
|
3
|
+
import urllib.request
|
|
4
|
+
from typing import Any, Callable, ParamSpec, TypeVar
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
from unique_stock_ticker.clients.six.exception import NoCredentialsException
|
|
9
|
+
from unique_stock_ticker.clients.six.http_adapter import InMemoryCertAdapter
|
|
10
|
+
from unique_stock_ticker.clients.six.schema import (
|
|
11
|
+
BaseRequestParams,
|
|
12
|
+
BaseResponsePayload,
|
|
13
|
+
)
|
|
14
|
+
from unique_stock_ticker.clients.six.schema.end_of_day_history import (
|
|
15
|
+
EndOfDayHistoryRequestParams,
|
|
16
|
+
EndOfDayHistoryResponsePayload,
|
|
17
|
+
)
|
|
18
|
+
from unique_stock_ticker.clients.six.schema.entity_base import (
|
|
19
|
+
EntityBaseByListingRequestParams,
|
|
20
|
+
EntityBaseByListingResponsePayload,
|
|
21
|
+
)
|
|
22
|
+
from unique_stock_ticker.clients.six.schema.free_text_search import (
|
|
23
|
+
FreeTextEntitiesSearchResponsePayload,
|
|
24
|
+
FreeTextInstrumentsSearchResponsePayload,
|
|
25
|
+
FreeTextMarketsSearchResponsePayload,
|
|
26
|
+
FreeTextSearchEntitiesRequestParams,
|
|
27
|
+
FreeTextSearchInstrumentsRequestParams,
|
|
28
|
+
FreeTextSearchMarketsRequestParams,
|
|
29
|
+
)
|
|
30
|
+
from unique_stock_ticker.clients.six.schema.intraday_history import (
|
|
31
|
+
IntradayHistorySummaryRequestParams,
|
|
32
|
+
IntradayHistorySummaryResponsePayload,
|
|
33
|
+
)
|
|
34
|
+
from unique_stock_ticker.clients.six.schema.intraday_snapshot import (
|
|
35
|
+
IntradaySnapshotRequestParams,
|
|
36
|
+
IntradaySnapshotResponsePayload,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
API_URL = "https://api.six-group.com/web/"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def split_cert_chain(cert: str) -> list[str]:
|
|
43
|
+
return re.findall(".*?-----END CERTIFICATE-----", cert, re.DOTALL)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SixApiClient:
|
|
47
|
+
def __init__(self, cert: str, key: str) -> None:
|
|
48
|
+
self._session = requests.Session()
|
|
49
|
+
self._session.headers = {"accept": "application/json"}
|
|
50
|
+
self._url = API_URL
|
|
51
|
+
|
|
52
|
+
certs = [c.encode("utf-8") for c in split_cert_chain(cert)]
|
|
53
|
+
cert_adapter = InMemoryCertAdapter(
|
|
54
|
+
certs[0],
|
|
55
|
+
key.encode("utf-8"),
|
|
56
|
+
certs[1:],
|
|
57
|
+
)
|
|
58
|
+
self._session.mount(self._url, cert_adapter)
|
|
59
|
+
|
|
60
|
+
# Endpoints
|
|
61
|
+
self.end_of_day_history = endpoint(
|
|
62
|
+
self,
|
|
63
|
+
"v1/listings/marketData/endOfDayHistory",
|
|
64
|
+
EndOfDayHistoryRequestParams,
|
|
65
|
+
EndOfDayHistoryResponsePayload,
|
|
66
|
+
)
|
|
67
|
+
self.free_text_search_instruments = endpoint(
|
|
68
|
+
self,
|
|
69
|
+
"v1/search/freeTextSearch/instruments",
|
|
70
|
+
FreeTextSearchInstrumentsRequestParams,
|
|
71
|
+
FreeTextInstrumentsSearchResponsePayload,
|
|
72
|
+
)
|
|
73
|
+
self.free_text_search_entities = endpoint(
|
|
74
|
+
self,
|
|
75
|
+
"v1/search/freeTextSearch/entities",
|
|
76
|
+
FreeTextSearchEntitiesRequestParams,
|
|
77
|
+
FreeTextEntitiesSearchResponsePayload,
|
|
78
|
+
)
|
|
79
|
+
self.free_text_search_markets = endpoint(
|
|
80
|
+
self,
|
|
81
|
+
"v1/search/freeTextSearch/markets",
|
|
82
|
+
FreeTextSearchMarketsRequestParams,
|
|
83
|
+
FreeTextMarketsSearchResponsePayload,
|
|
84
|
+
)
|
|
85
|
+
self.intraday_history_summary = endpoint(
|
|
86
|
+
self,
|
|
87
|
+
"v1/listings/marketData/intradayHistory/summary",
|
|
88
|
+
IntradayHistorySummaryRequestParams,
|
|
89
|
+
IntradayHistorySummaryResponsePayload,
|
|
90
|
+
)
|
|
91
|
+
self.intraday_snapshot = endpoint(
|
|
92
|
+
self,
|
|
93
|
+
"v1/listings/marketData/intradaySnapshot",
|
|
94
|
+
IntradaySnapshotRequestParams,
|
|
95
|
+
IntradaySnapshotResponsePayload,
|
|
96
|
+
)
|
|
97
|
+
self.entity_base_by_listing = endpoint(
|
|
98
|
+
self,
|
|
99
|
+
"v1/listings/referenceData/entityBase",
|
|
100
|
+
EntityBaseByListingRequestParams,
|
|
101
|
+
EntityBaseByListingResponsePayload,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def request(
|
|
105
|
+
self, end_point: str, params: dict[str, Any]
|
|
106
|
+
) -> dict[str, Any]:
|
|
107
|
+
complete_url = (
|
|
108
|
+
f"{self._url}{end_point}?{urllib.parse.urlencode(params)}"
|
|
109
|
+
)
|
|
110
|
+
response = self._session.get(complete_url)
|
|
111
|
+
response.raise_for_status()
|
|
112
|
+
return response.json()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
ResponseType = TypeVar("ResponseType", bound=BaseResponsePayload)
|
|
116
|
+
RequestType = TypeVar("RequestType", bound=BaseRequestParams)
|
|
117
|
+
RequestConstructorSpec = ParamSpec("RequestConstructorSpec")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def endpoint(
|
|
121
|
+
client: SixApiClient,
|
|
122
|
+
url: str,
|
|
123
|
+
params: Callable[RequestConstructorSpec, RequestType],
|
|
124
|
+
response_type: type[ResponseType],
|
|
125
|
+
) -> Callable[RequestConstructorSpec, ResponseType]:
|
|
126
|
+
def endpoint_f(
|
|
127
|
+
*args: RequestConstructorSpec.args,
|
|
128
|
+
**kwargs: RequestConstructorSpec.kwargs,
|
|
129
|
+
) -> ResponseType:
|
|
130
|
+
return response_type.model_validate(
|
|
131
|
+
client.request(
|
|
132
|
+
url,
|
|
133
|
+
params(*args, **kwargs).model_dump(
|
|
134
|
+
exclude_unset=True,
|
|
135
|
+
by_alias=True,
|
|
136
|
+
exclude_defaults=True,
|
|
137
|
+
mode="json",
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return endpoint_f
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_six_api_client(company_id: str) -> SixApiClient:
|
|
146
|
+
from unique_stock_ticker.clients.six.settings import six_api_settings
|
|
147
|
+
|
|
148
|
+
creds = six_api_settings.creds_for_company(company_id)
|
|
149
|
+
if creds is None:
|
|
150
|
+
raise NoCredentialsException(company_id)
|
|
151
|
+
return SixApiClient(creds.cert, creds.key)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from unique_toolkit.stock_ticker.clients.six.schema.common.base.response import BaseResponsePayload, ErrorDetail
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def error_to_str(error: ErrorDetail) -> str:
|
|
7
|
+
error_str = f"Error {error.code}: {error.category}"
|
|
8
|
+
if error.message:
|
|
9
|
+
error_str += f": {error.message}"
|
|
10
|
+
return error_str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SixApiException(Exception):
|
|
14
|
+
def __init__(self, errors: list[ErrorDetail]) -> None:
|
|
15
|
+
self.errors = errors
|
|
16
|
+
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
if len(self.errors) == 1:
|
|
19
|
+
return error_to_str(self.errors[0])
|
|
20
|
+
else:
|
|
21
|
+
return "Errors:\n" + "\n".join(
|
|
22
|
+
[error_to_str(error) for error in self.errors]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def raise_errors_from_api_response(response: BaseResponsePayload) -> None:
|
|
27
|
+
if response.errors is not None and len(response.errors) > 0:
|
|
28
|
+
raise SixApiException(response.errors)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NoCredentialsException(Exception): ...
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
show_usage() {
|
|
6
|
+
cat <<EOF
|
|
7
|
+
Usage: $0 --cert|-c CERT_FILE --key|-k KEY_FILE [--output|-o OUTPUT_FILE]
|
|
8
|
+
|
|
9
|
+
Encode certificate and key files into a base64-encoded string
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
-c, --cert CERT_FILE Path to the certificate file
|
|
13
|
+
-k, --key KEY_FILE Path to the private key file
|
|
14
|
+
-o, --output OUTPUT_FILE Output file to write the result (if not specified, prints to stdout)
|
|
15
|
+
-h, --help Show this help message
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
$0 --cert cert.pem --key key.pem
|
|
19
|
+
$0 -c /path/to/cert.crt -k /path/to/private.key
|
|
20
|
+
$0 -c cert.pem -k key.pem -o encoded_creds.txt
|
|
21
|
+
EOF
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get_creds() {
|
|
25
|
+
local cert_path="$1"
|
|
26
|
+
local key_path="$2"
|
|
27
|
+
|
|
28
|
+
if [[ ! -f "$cert_path" ]]; then
|
|
29
|
+
echo "Error: Certificate file not found: $cert_path" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [[ ! -f "$key_path" ]]; then
|
|
34
|
+
echo "Error: Key file not found: $key_path" >&2
|
|
35
|
+
exit 1
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if ! cert_content=$(cat "$cert_path" 2>/dev/null); then
|
|
39
|
+
echo "Error reading certificate file: $cert_path" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if ! key_content=$(cat "$key_path" 2>/dev/null); then
|
|
44
|
+
echo "Error reading key file: $key_path" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
cert_escaped=$(printf '%s' "$cert_content" | tr -d '\n')
|
|
49
|
+
key_escaped=$(printf '%s' "$key_content" | tr -d '\n')
|
|
50
|
+
|
|
51
|
+
content="[\"$cert_escaped\",\"$key_escaped\"]"
|
|
52
|
+
echo -n "$content" | base64
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
cert_path=""
|
|
56
|
+
key_path=""
|
|
57
|
+
output_file=""
|
|
58
|
+
|
|
59
|
+
while [[ $# -gt 0 ]]; do
|
|
60
|
+
case $1 in
|
|
61
|
+
-c | --cert)
|
|
62
|
+
cert_path="$2"
|
|
63
|
+
shift 2
|
|
64
|
+
;;
|
|
65
|
+
-k | --key)
|
|
66
|
+
key_path="$2"
|
|
67
|
+
shift 2
|
|
68
|
+
;;
|
|
69
|
+
-o | --output)
|
|
70
|
+
output_file="$2"
|
|
71
|
+
shift 2
|
|
72
|
+
;;
|
|
73
|
+
-h | --help)
|
|
74
|
+
show_usage
|
|
75
|
+
exit 0
|
|
76
|
+
;;
|
|
77
|
+
*)
|
|
78
|
+
echo "Error: Unknown option $1" >&2
|
|
79
|
+
show_usage
|
|
80
|
+
exit 1
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
if [[ -z "$cert_path" ]]; then
|
|
86
|
+
echo "Error: Certificate file path is required" >&2
|
|
87
|
+
show_usage
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [[ -z "$key_path" ]]; then
|
|
92
|
+
echo "Error: Key file path is required" >&2
|
|
93
|
+
show_usage
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if ! encoded_creds=$(get_creds "$cert_path" "$key_path"); then
|
|
98
|
+
echo "Error processing files" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [[ -n "$output_file" ]]; then
|
|
103
|
+
if ! echo "$encoded_creds" >"$output_file"; then
|
|
104
|
+
echo "Error writing to output file: $output_file" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
echo "Encoded credentials written to: $output_file"
|
|
108
|
+
else
|
|
109
|
+
echo "$encoded_creds"
|
|
110
|
+
fi
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|
4
|
+
from cryptography.x509 import load_pem_x509_certificate
|
|
5
|
+
from requests.adapters import HTTPAdapter
|
|
6
|
+
from urllib3.contrib.pyopenssl import (
|
|
7
|
+
PyOpenSSLContext,
|
|
8
|
+
)
|
|
9
|
+
from urllib3.util.ssl_ import PROTOCOL_TLS_CLIENT
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class InMemoryCertAdapter(HTTPAdapter):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
cert: bytes,
|
|
16
|
+
key: bytes,
|
|
17
|
+
chain_certs: Iterable[bytes] | None = None,
|
|
18
|
+
*args,
|
|
19
|
+
**kwargs,
|
|
20
|
+
) -> None:
|
|
21
|
+
self._chain_certs = tuple(chain_certs) if chain_certs else None
|
|
22
|
+
self._cert = cert
|
|
23
|
+
self._key = key
|
|
24
|
+
super().__init__(*args, **kwargs)
|
|
25
|
+
|
|
26
|
+
def init_poolmanager(self, *args, **kwargs):
|
|
27
|
+
context = self._create_ssl_context()
|
|
28
|
+
kwargs["ssl_context"] = context
|
|
29
|
+
return super().init_poolmanager(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
def _create_ssl_context(self) -> PyOpenSSLContext:
|
|
32
|
+
context = PyOpenSSLContext(PROTOCOL_TLS_CLIENT)
|
|
33
|
+
context.set_default_verify_paths()
|
|
34
|
+
|
|
35
|
+
context._ctx.use_certificate(load_pem_x509_certificate(self._cert))
|
|
36
|
+
if self._chain_certs is not None:
|
|
37
|
+
for c in self._chain_certs:
|
|
38
|
+
context._ctx.add_extra_chain_cert(load_pem_x509_certificate(c))
|
|
39
|
+
|
|
40
|
+
context._ctx.use_privatekey(
|
|
41
|
+
load_pem_private_key(self._key, password=None) # type: ignore
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Throw an exception if the private key is not valid
|
|
45
|
+
context._ctx.check_privatekey()
|
|
46
|
+
|
|
47
|
+
return context
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from unique_stock_ticker.clients.six.schema.common.base import (
|
|
2
|
+
BaseAPIModel,
|
|
3
|
+
BaseRequestParams,
|
|
4
|
+
BaseResponsePayload,
|
|
5
|
+
)
|
|
6
|
+
from unique_stock_ticker.clients.six.schema.common.entity import (
|
|
7
|
+
EntityStatus,
|
|
8
|
+
EntityType,
|
|
9
|
+
)
|
|
10
|
+
from unique_stock_ticker.clients.six.schema.common.instrument import (
|
|
11
|
+
ContractType,
|
|
12
|
+
ContractUnitType,
|
|
13
|
+
CurrentCouponType,
|
|
14
|
+
ExerciseType,
|
|
15
|
+
InstrumentStatus,
|
|
16
|
+
InstrumentType,
|
|
17
|
+
InstrumentUnitType,
|
|
18
|
+
MaturityType,
|
|
19
|
+
OptionType,
|
|
20
|
+
)
|
|
21
|
+
from unique_stock_ticker.clients.six.schema.common.language import Language
|
|
22
|
+
from unique_stock_ticker.clients.six.schema.common.listing import (
|
|
23
|
+
ListingIdentifierScheme,
|
|
24
|
+
ListingStatus,
|
|
25
|
+
)
|
|
26
|
+
from unique_stock_ticker.clients.six.schema.common.lookup import LookupStatus
|
|
27
|
+
from unique_stock_ticker.clients.six.schema.common.market import (
|
|
28
|
+
MarketStatus,
|
|
29
|
+
MarketType,
|
|
30
|
+
MicType,
|
|
31
|
+
)
|
|
32
|
+
from unique_stock_ticker.clients.six.schema.common.price import PriceAdjustment
|
|
33
|
+
from unique_stock_ticker.clients.six.schema.common.security import (
|
|
34
|
+
SecurityType,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"BaseAPIModel",
|
|
39
|
+
"BaseRequestParams",
|
|
40
|
+
"BaseResponsePayload",
|
|
41
|
+
"ListingIdentifierScheme",
|
|
42
|
+
"InstrumentType",
|
|
43
|
+
"InstrumentStatus",
|
|
44
|
+
"InstrumentUnitType",
|
|
45
|
+
"SecurityType",
|
|
46
|
+
"Language",
|
|
47
|
+
"ContractType",
|
|
48
|
+
"CurrentCouponType",
|
|
49
|
+
"ContractUnitType",
|
|
50
|
+
"OptionType",
|
|
51
|
+
"ExerciseType",
|
|
52
|
+
"MaturityType",
|
|
53
|
+
"EntityType",
|
|
54
|
+
"EntityStatus",
|
|
55
|
+
"MarketType",
|
|
56
|
+
"MicType",
|
|
57
|
+
"MarketStatus",
|
|
58
|
+
"PriceAdjustment",
|
|
59
|
+
"ListingStatus",
|
|
60
|
+
"LookupStatus",
|
|
61
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from unique_stock_ticker.clients.six.schema.common.base.model import BaseAPIModel
|
|
2
|
+
from unique_stock_ticker.clients.six.schema.common.base.request import BaseRequestParams
|
|
3
|
+
from unique_stock_ticker.clients.six.schema.common.base.response import (
|
|
4
|
+
BaseResponsePayload,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
__all__ = ["BaseAPIModel", "BaseRequestParams", "BaseResponsePayload"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from enum import StrEnum
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from unique_stock_ticker.clients.six.schema.common.base.model import BaseAPIModel
|
|
6
|
+
from unique_stock_ticker.clients.six.schema.common.language import Language
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RequestExtension(StrEnum):
|
|
10
|
+
EXPLAIN = "EXPLAIN"
|
|
11
|
+
DATA_STATUS = "DATA_STATUS"
|
|
12
|
+
DATASET_IDS = "DATASET_IDS"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseRequestParams(BaseAPIModel):
|
|
16
|
+
preferred_language: Language = Field(default=Language.EN)
|
|
17
|
+
extensions: list[RequestExtension] = Field(default=[])
|