RAPTOR v18.4: Исправлена отчетность, активированы выходные

This commit is contained in:
root
2026-04-18 23:26:45 +03:00
commit ef0958239e
312 changed files with 54247 additions and 0 deletions

View File

@@ -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, **{})

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,13 @@
from typing import List
class InstrumentResponse:
class_code: str
figi: str
ticker: str
uid: str
class InstrumentsResponse:
instruments: List[InstrumentResponse]

View File

@@ -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:
...

View File

@@ -0,0 +1,7 @@
import dataclasses
from datetime import timedelta
@dataclasses.dataclass()
class InstrumentsCacheSettings:
ttl: timedelta = timedelta(days=1)