RAPTOR v18.4: Исправлена отчетность, активированы выходные
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import logging
|
||||
from dataclasses import replace
|
||||
from typing import Dict, Generic, Tuple, TypeVar, cast
|
||||
|
||||
from t_tech.invest import InstrumentIdType
|
||||
from t_tech.invest.caching.instruments_cache.models import (
|
||||
InstrumentResponse,
|
||||
InstrumentsResponse,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
TInstrumentResponse = TypeVar("TInstrumentResponse", bound=InstrumentResponse)
|
||||
TInstrumentsResponse = TypeVar("TInstrumentsResponse", bound=InstrumentsResponse)
|
||||
|
||||
|
||||
class InstrumentStorage(Generic[TInstrumentResponse, TInstrumentsResponse]):
|
||||
def __init__(self, instruments_response: TInstrumentsResponse):
|
||||
self._instruments_response = instruments_response
|
||||
|
||||
self._instrument_by_class_code_figi: Dict[
|
||||
Tuple[str, str], InstrumentResponse
|
||||
] = {
|
||||
(instrument.class_code, instrument.figi): instrument
|
||||
for instrument in self._instruments_response.instruments
|
||||
}
|
||||
self._instrument_by_class_code_ticker: Dict[
|
||||
Tuple[str, str], InstrumentResponse
|
||||
] = {
|
||||
(instrument.class_code, instrument.ticker): instrument
|
||||
for instrument in self._instruments_response.instruments
|
||||
}
|
||||
self._instrument_by_class_code_uid: Dict[
|
||||
Tuple[str, str], InstrumentResponse
|
||||
] = {
|
||||
(instrument.class_code, instrument.uid): instrument
|
||||
for instrument in self._instruments_response.instruments
|
||||
}
|
||||
|
||||
# fmt: off
|
||||
self._instrument_by_class_code_id_index = {
|
||||
InstrumentIdType.INSTRUMENT_ID_UNSPECIFIED:
|
||||
self._instrument_by_class_code_figi,
|
||||
InstrumentIdType.INSTRUMENT_ID_TYPE_FIGI:
|
||||
self._instrument_by_class_code_figi,
|
||||
InstrumentIdType.INSTRUMENT_ID_TYPE_TICKER:
|
||||
self._instrument_by_class_code_ticker,
|
||||
InstrumentIdType.INSTRUMENT_ID_TYPE_UID:
|
||||
self._instrument_by_class_code_uid,
|
||||
}
|
||||
# fmt: on
|
||||
|
||||
def get(
|
||||
self, *, id_type: InstrumentIdType, class_code: str, id: str
|
||||
) -> TInstrumentResponse:
|
||||
logger.debug(
|
||||
"Cache request id_type=%s, class_code=%s, id=%s", id_type, class_code, id
|
||||
)
|
||||
instrument_by_class_code_id = self._instrument_by_class_code_id_index[id_type]
|
||||
logger.debug(
|
||||
"Index for %s found: \n%s", id_type, instrument_by_class_code_id.keys()
|
||||
)
|
||||
key = (class_code, id)
|
||||
logger.debug("Cache request key=%s", key)
|
||||
|
||||
return cast(TInstrumentResponse, instrument_by_class_code_id[key])
|
||||
|
||||
def get_instruments_response(self) -> TInstrumentsResponse:
|
||||
return replace(self._instruments_response, **{})
|
||||
@@ -0,0 +1,203 @@
|
||||
import logging
|
||||
from typing import cast
|
||||
|
||||
from t_tech.invest import (
|
||||
Bond,
|
||||
BondResponse,
|
||||
BondsResponse,
|
||||
CurrenciesResponse,
|
||||
Currency,
|
||||
CurrencyResponse,
|
||||
Etf,
|
||||
EtfResponse,
|
||||
EtfsResponse,
|
||||
Future,
|
||||
FutureResponse,
|
||||
FuturesResponse,
|
||||
InstrumentIdType,
|
||||
InstrumentStatus,
|
||||
Share,
|
||||
ShareResponse,
|
||||
SharesResponse,
|
||||
)
|
||||
from t_tech.invest.caching.instruments_cache.instrument_storage import InstrumentStorage
|
||||
from t_tech.invest.caching.instruments_cache.interface import IInstrumentsGetter
|
||||
from t_tech.invest.caching.instruments_cache.models import (
|
||||
InstrumentResponse,
|
||||
InstrumentsResponse,
|
||||
)
|
||||
from t_tech.invest.caching.instruments_cache.protocol import InstrumentsResponseCallable
|
||||
from t_tech.invest.caching.instruments_cache.settings import InstrumentsCacheSettings
|
||||
from t_tech.invest.caching.overrides import TTLCache
|
||||
from t_tech.invest.services import InstrumentsService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InstrumentsCache(IInstrumentsGetter):
|
||||
def __init__(
|
||||
self,
|
||||
settings: InstrumentsCacheSettings,
|
||||
instruments_service: InstrumentsService,
|
||||
):
|
||||
self._settings = settings
|
||||
self._instruments_service = instruments_service
|
||||
|
||||
logger.debug("Initialising instruments cache")
|
||||
self._instruments_methods = [
|
||||
self.shares,
|
||||
self.futures,
|
||||
self.etfs,
|
||||
self.bonds,
|
||||
self.currencies,
|
||||
]
|
||||
self._cache: TTLCache = TTLCache(
|
||||
maxsize=len(self._instruments_methods),
|
||||
ttl=self._settings.ttl.total_seconds(),
|
||||
)
|
||||
self._refresh_cache()
|
||||
|
||||
def _refresh_cache(self):
|
||||
logger.debug("Refreshing instruments cache")
|
||||
for instruments_method in self._instruments_methods:
|
||||
instruments_method()
|
||||
self._assert_cache()
|
||||
|
||||
def _assert_cache(self):
|
||||
if self._cache.keys() != {f.__name__ for f in self._instruments_methods}:
|
||||
raise KeyError(f"Cache does not have all instrument types {self._cache}")
|
||||
|
||||
def _get_instrument_storage(
|
||||
self, get_instruments_method: InstrumentsResponseCallable
|
||||
) -> InstrumentStorage[InstrumentResponse, InstrumentsResponse]:
|
||||
storage_key = get_instruments_method.__name__
|
||||
storage = self._cache.get(storage_key)
|
||||
if storage is not None:
|
||||
logger.debug("Got storage for key %s from cache", storage_key)
|
||||
return storage
|
||||
logger.debug(
|
||||
"Storage for key %s not found, creating new storage with ttl=%s",
|
||||
storage_key,
|
||||
self._cache.ttl,
|
||||
)
|
||||
instruments_response = get_instruments_method(
|
||||
instrument_status=InstrumentStatus.INSTRUMENT_STATUS_ALL
|
||||
)
|
||||
storage = InstrumentStorage(instruments_response=instruments_response)
|
||||
self._cache[storage_key] = storage
|
||||
return storage # noqa:R504
|
||||
|
||||
def shares(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> SharesResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[ShareResponse, SharesResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.shares),
|
||||
)
|
||||
return storage.get_instruments_response()
|
||||
|
||||
def share_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> ShareResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[Share, SharesResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.shares),
|
||||
)
|
||||
share = storage.get(id_type=id_type, class_code=class_code, id=id)
|
||||
return ShareResponse(instrument=share)
|
||||
|
||||
def futures(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> FuturesResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[FutureResponse, FuturesResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.futures),
|
||||
)
|
||||
return storage.get_instruments_response()
|
||||
|
||||
def future_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> FutureResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[Future, FuturesResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.futures),
|
||||
)
|
||||
future = storage.get(id_type=id_type, class_code=class_code, id=id)
|
||||
return FutureResponse(instrument=future)
|
||||
|
||||
def etfs(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> EtfsResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[EtfResponse, EtfsResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.etfs),
|
||||
)
|
||||
return storage.get_instruments_response()
|
||||
|
||||
def etf_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> EtfResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[Etf, EtfsResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.etfs),
|
||||
)
|
||||
etf = storage.get(id_type=id_type, class_code=class_code, id=id)
|
||||
return EtfResponse(instrument=etf)
|
||||
|
||||
def bonds(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> BondsResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[BondResponse, BondsResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.bonds),
|
||||
)
|
||||
return storage.get_instruments_response()
|
||||
|
||||
def bond_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> BondResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[Bond, BondsResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.bonds),
|
||||
)
|
||||
bond = storage.get(id_type=id_type, class_code=class_code, id=id)
|
||||
return BondResponse(instrument=bond)
|
||||
|
||||
def currencies(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> CurrenciesResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[CurrencyResponse, CurrenciesResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.currencies),
|
||||
)
|
||||
return storage.get_instruments_response()
|
||||
|
||||
def currency_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> CurrencyResponse:
|
||||
storage = cast(
|
||||
InstrumentStorage[Currency, CurrenciesResponse], # type: ignore
|
||||
self._get_instrument_storage(self._instruments_service.currencies),
|
||||
)
|
||||
currency = storage.get(id_type=id_type, class_code=class_code, id=id)
|
||||
return CurrencyResponse(instrument=currency)
|
||||
@@ -0,0 +1,98 @@
|
||||
import abc
|
||||
|
||||
from t_tech.invest import (
|
||||
BondResponse,
|
||||
BondsResponse,
|
||||
CurrenciesResponse,
|
||||
CurrencyResponse,
|
||||
EtfResponse,
|
||||
EtfsResponse,
|
||||
FutureResponse,
|
||||
FuturesResponse,
|
||||
InstrumentIdType,
|
||||
InstrumentStatus,
|
||||
ShareResponse,
|
||||
SharesResponse,
|
||||
)
|
||||
|
||||
|
||||
class IInstrumentsGetter(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def shares(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> SharesResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def share_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> ShareResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def futures(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> FuturesResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def future_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> FutureResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def etfs(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> EtfsResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def etf_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> EtfResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def bonds(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> BondsResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def bond_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> BondResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def currencies(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> CurrenciesResponse:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def currency_by(
|
||||
self,
|
||||
*,
|
||||
id_type: InstrumentIdType = InstrumentIdType(0),
|
||||
class_code: str = "",
|
||||
id: str = "",
|
||||
) -> CurrencyResponse:
|
||||
pass
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
class InstrumentResponse:
|
||||
class_code: str
|
||||
|
||||
figi: str
|
||||
ticker: str
|
||||
uid: str
|
||||
|
||||
|
||||
class InstrumentsResponse:
|
||||
instruments: List[InstrumentResponse]
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import Protocol
|
||||
|
||||
from t_tech.invest import InstrumentStatus
|
||||
from t_tech.invest.caching.instruments_cache.models import InstrumentsResponse
|
||||
|
||||
|
||||
class InstrumentsResponseCallable(Protocol):
|
||||
def __call__(
|
||||
self, *, instrument_status: InstrumentStatus = InstrumentStatus(0)
|
||||
) -> InstrumentsResponse:
|
||||
...
|
||||
|
||||
def __name__(self) -> str:
|
||||
...
|
||||
@@ -0,0 +1,7 @@
|
||||
import dataclasses
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
@dataclasses.dataclass()
|
||||
class InstrumentsCacheSettings:
|
||||
ttl: timedelta = timedelta(days=1)
|
||||
Reference in New Issue
Block a user