# Build 012 — Instrument Acquisition Service

**Статус:** Завершён  
**Подсистема:** `market_data/acquisition`  
**Область:** Instrument Reference Data  
**Тип изменения:** Изолированное добавление application-level сервиса получения справочника инструментов без подключения к production runtime  
**Результат полного набора тестов:** `180 passed`

---

## 1. Цель Build 012

Цель Build 012 — реализовать application-level сервис, который получает Instrument Feed из Registry и запускает загрузку справочника инструментов.

Реализован класс:

```text
InstrumentAcquisitionService
```

Его архитектурная граница:

```text
source_name
    ↓
InstrumentAcquisitionService
    ↓
InstrumentFeedRegistry
    ↓
InstrumentFeedProtocol
    ↓
load_instruments()
    ↓
tuple[Instrument, ...]
```

Build 012 завершает orchestration-слой новой изолированной acquisition pipeline.

На этом этапе сервис:

```text
получает Feed из Registry;
вызывает Feed;
возвращает результат Feed без изменения;
сохраняет специализированные ошибки без wrapping.
```

Build 012 не подключается к существующему `ExchangeService`, не меняет legacy runtime и не переключает production-потребителей на новую реализацию.

---

## 2. Почему Build 012 выполняется именно сейчас

До начала Build 012 были завершены:

```text
Build 001 — внутренняя модель Instrument Reference Data
Build 002 — raw-модели ответа Dzengi
Build 003 — структурная валидация exchangeInfo
Build 004 — parser exchangeInfo
Build 005 — value validation
Build 006 — mapper Dzengi → Instrument
Build 007 — Protocol и Exceptions
Build 008 — Dzengi REST Adapter
Build 009 — Instrument Handler
Build 010 — Instrument Feed
Build 011 — Instrument Feed Registry
```

После Build 011 уже существовала цепочка:

```text
source_name
    ↓
InstrumentFeedRegistry
    ↓
InstrumentFeedProtocol
```

И отдельно существовал полный Feed pipeline:

```text
InstrumentFeed
    ↓
InstrumentDocumentSource
    ↓
DzengiInstrumentDocumentSource
    ↓
Dzengi REST API
    ↓
raw document
    ↓
InstrumentDocumentHandler
    ↓
DzengiInstrumentDocumentHandler
    ↓
schema validation
    ↓
parser
    ↓
value validation
    ↓
mapper
    ↓
tuple[Instrument, ...]
```

Однако отсутствовала application-level точка входа, соединяющая Registry с запуском Feed.

Build 012 создаёт эту точку:

```text
InstrumentAcquisitionService
```

---

## 3. Архитектурная ответственность Acquisition Service

Единственная предметная ответственность сервиса:

```text
получить source_name
    ↓
получить Feed через Registry
    ↓
вызвать load_instruments()
    ↓
вернуть tuple[Instrument, ...]
```

Минимальная логика сервиса:

```python
feed = self._registry.get(source_name)

return feed.load_instruments()
```

Сервис не содержит собственной transport-, parsing-, validation-, mapping- или storage-логики.

---

## 4. Изменённые файлы

В рамках Build 012 реализован:

```text
app/src/market_data/acquisition/service.py
```

Создан тестовый файл:

```text
app/tests/unit/market_data/acquisition/test_service.py
```

Другие production-файлы не изменялись.

В частности, не изменялись:

```text
app/src/market_data/acquisition/exceptions.py
app/src/market_data/acquisition/protocol.py
app/src/market_data/acquisition/registry.py
app/src/market_data/acquisition/feeds/instrument_feed.py

app/src/market_data/acquisition/adapters/dzengi/rest.py
app/src/market_data/acquisition/handlers/instrument_handler.py

app/src/integrations/exchange/*
app/src/telegram/*
app/src/trading/*
```

Re-export в `__init__.py` не добавлялся.

---

## 5. Реализованный InstrumentAcquisitionService

В файле:

```text
app/src/market_data/acquisition/service.py
```

реализован класс:

```python
class InstrumentAcquisitionService:
    ...
```

Его публичный контракт:

```python
def load_instruments(
    self,
    source_name: str,
) -> tuple[Instrument, ...]:
    ...
```

Полная схема:

```text
InstrumentAcquisitionService.load_instruments(source_name)
    ↓
InstrumentFeedRegistry.get(source_name)
    ↓
InstrumentFeedProtocol
    ↓
InstrumentFeedProtocol.load_instruments()
    ↓
tuple[Instrument, ...]
```

---

## 6. Явная dependency injection Registry

Registry передаётся сервису через конструктор:

```python
def __init__(
    self,
    *,
    registry: InstrumentFeedRegistry,
) -> None:
    self._registry = registry
```

Правильная композиция:

```python
registry = InstrumentFeedRegistry()

registry.register(
    "dzengi",
    feed,
)

service = InstrumentAcquisitionService(
    registry=registry,
)
```

После этого:

```python
instruments = service.load_instruments("dzengi")
```

Сервис не создаёт Registry самостоятельно.

---

## 7. Почему Service не создаёт Registry

Внутри `InstrumentAcquisitionService` отсутствует:

```python
self._registry = InstrumentFeedRegistry()
```

Это принципиальное архитектурное решение.

Если бы Service самостоятельно создавал Registry:

```text
Service
    ↓
создаёт Registry
    ↓
Registry изначально пуст
    ↓
требуется скрытая регистрация Feed
    ↓
Service начинает знать о конкретных источниках
```

Вместо этого используется:

```text
готовый Registry
    ↓
передаётся Service
```

Это обеспечивает:

```text
явные зависимости;
контролируемую composition;
независимость от конкретного источника;
простое тестирование;
отсутствие скрытой инициализации.
```

---

## 8. Независимость от Dzengi

`InstrumentAcquisitionService` не импортирует и не создаёт:

```text
DzengiInstrumentDocumentSource
DzengiInstrumentDocumentHandler
DzengiExchangeInfoResponse
ExchangeRestClient
InstrumentFeed
Dzengi parser
Dzengi mapper
```

Сервис зависит только от:

```text
Instrument
InstrumentFeedRegistry
```

Архитектурная схема:

```text
InstrumentAcquisitionService
    ↓
InstrumentFeedRegistry
    ↓
InstrumentFeedProtocol
```

Конкретный источник определяется снаружи через регистрацию Feed.

---

## 9. Передача source_name без изменения

Service не выполняет над `source_name`:

```text
strip()
lower()
casefold()
replace()
alias resolution
automatic source mapping
```

Он непосредственно передаёт полученное значение Registry:

```python
feed = self._registry.get(source_name)
```

Например:

```text
"  dzengi  "
```

передаётся в Registry именно как:

```text
"  dzengi  "
```

Нормализация внешних пробелов является ответственностью:

```text
InstrumentFeedRegistry
```

Это исключает дублирование правил между Service и Registry.

---

## 10. Получение Feed через Registry

Service не хранит Feed напрямую.

Отсутствует:

```python
self._feed = feed
```

Вместо этого для каждого вызова:

```python
service.load_instruments(source_name)
```

выполняется:

```python
feed = self._registry.get(source_name)
```

Таким образом:

```text
source_name
    ↓
Registry
    ↓
соответствующий Feed
```

Service не определяет самостоятельно, какой Feed использовать.

---

## 11. Однократный вызов Registry

Для одного вызова:

```python
service.load_instruments("dzengi")
```

метод:

```python
registry.get("dzengi")
```

вызывается ровно один раз.

Отсутствуют:

```text
повторный lookup;
предварительная проверка наличия;
двойной get();
fallback lookup.
```

Это подтверждено unit-тестом.

---

## 12. Однократный вызов Feed

После успешного получения Feed выполняется:

```python
feed.load_instruments()
```

ровно один раз.

Цепочка:

```text
Service.load_instruments()
    ↓
Registry.get()
    ↓
Feed.load_instruments()
    ↓
return result
```

Отсутствуют:

```text
retry;
повторный вызов после ошибки;
предварительный вызов;
дополнительная проверочная загрузка.
```

---

## 13. Возврат результата без копирования

Service непосредственно возвращает:

```python
return feed.load_instruments()
```

Он не выполняет:

```python
tuple(feed.load_instruments())
```

или:

```python
result = feed.load_instruments()
return tuple(result)
```

Поэтому сохраняется identity результата:

```python
result is instruments
```

равно:

```text
True
```

Это подтверждено unit-тестами.

---

## 14. Сохранение порядка инструментов

Service не выполняет:

```text
sorting;
filtering;
deduplication;
grouping;
reordering.
```

Если Feed возвращает:

```text
BTC/USD_LEVERAGE
ETH/USD_LEVERAGE
XRP/USD_LEVERAGE
```

Service возвращает инструменты в том же порядке:

```text
BTC/USD_LEVERAGE
ETH/USD_LEVERAGE
XRP/USD_LEVERAGE
```

Порядок результата Feed сохраняется.

---

## 15. Поведение при пустом результате

Если Feed возвращает:

```python
()
```

Service также возвращает:

```python
()
```

без ошибки.

Service не интерпретирует пустой результат как:

```text
transport error;
schema error;
value error;
mapping error;
отсутствие источника.
```

На Build 012 отсутствует утверждённое правило, согласно которому пустой `tuple` должен считаться ошибкой.

---

## 16. Сохранение специализированных ошибок

В `InstrumentAcquisitionService` отсутствует общий `try/except`, который заменял бы исходные ошибки новой общей ошибкой.

Без изменения могут пройти:

```text
InstrumentFeedRegistryError
InstrumentReferenceTransportError
InstrumentReferenceSchemaError
InstrumentReferenceParseError
InstrumentReferenceValueError
InstrumentReferenceMappingError
```

Схема:

```text
Registry error
    ↓
Service
    ↓
та же Registry error
```

или:

```text
Feed error
    ↓
Service
    ↓
та же Feed error
```

---

## 17. Сохранение identity ошибки

Тесты подтверждают не только тип ошибки, но и сохранение исходного объекта.

Если Feed выбрасывает:

```python
original_error = InstrumentReferenceTransportError(
    "Network error."
)
```

то Service передаёт именно этот объект:

```python
exc_info.value is original_error
```

равно:

```text
True
```

Ошибка не:

```text
копируется;
оборачивается;
заменяется;
переводится в другой тип.
```

---

## 18. Ошибка отсутствующего Feed

Если Registry не содержит источник:

```text
"dzengi"
```

вызов:

```python
service.load_instruments("dzengi")
```

приводит к:

```text
InstrumentFeedRegistryError
```

Service не выполняет:

```text
fallback;
создание Feed;
регистрацию Feed;
использование default source;
возврат пустого tuple.
```

Ошибка Registry проходит наружу без wrapping.

---

## 19. Feed не вызывается при ошибке Registry

Последовательность:

```text
Service.load_instruments()
    ↓
Registry.get()
    ↓
InstrumentFeedRegistryError
```

останавливается на ошибке Registry.

Метод:

```text
feed.load_instruments()
```

не вызывается.

Это подтверждено unit-тестом.

---

## 20. Отсутствие retry после ошибки Feed

Если Feed выбрасывает:

```text
InstrumentReferenceTransportError
```

Service не повторяет вызов.

Схема:

```text
feed.load_instruments()
    ↓
InstrumentReferenceTransportError
    ↓
ошибка немедленно выходит из Service
```

Счётчик вызовов Feed остаётся:

```text
1
```

Таким образом, Build 012 не вводит скрытую retry policy.

---

## 21. Почему retry отсутствует

Retry является отдельной operational policy.

Для его корректной реализации необходимо отдельно определить:

```text
какие ошибки являются retryable;
максимальное число попыток;
интервалы между попытками;
backoff;
jitter;
timeout budget;
логирование повторных попыток;
поведение при исчерпании попыток.
```

Build 012 не должен неявно принимать эти архитектурные решения.

Поэтому:

```text
один вызов Service
    ↓
один вызов Feed
```

---

## 22. Почему не создан новый Service exception

В Build 012 не добавлен:

```text
InstrumentAcquisitionServiceError
```

Уже существуют специализированные ошибки:

```text
InstrumentFeedRegistryError
InstrumentReferenceTransportError
InstrumentReferenceSchemaError
InstrumentReferenceParseError
InstrumentReferenceValueError
InstrumentReferenceMappingError
```

Создание общей ошибки:

```text
InstrumentAcquisitionServiceError
```

и wrapping всех причин в неё ухудшило бы диагностируемость.

Поэтому сохраняется точная причина отказа.

---

## 23. Что Service не хранит

Внутри `InstrumentAcquisitionService` отсутствуют:

```text
tuple[Instrument, ...];
последний успешный справочник;
предыдущий snapshot;
timestamp;
TTL;
cache age;
последняя ошибка;
индекс инструментов;
legacy ExchangeSymbol.
```

Service хранит только зависимость:

```text
InstrumentFeedRegistry
```

---

## 24. Что Service не делает

Build 012 сознательно не выполняет:

```text
REST-запросы напрямую;
получение exchangeInfo напрямую;
schema validation;
parsing;
value validation;
mapping;
создание Registry;
создание Feed;
создание Source;
создание Handler;
автоматическую регистрацию Feed;
retry;
backoff;
кэширование;
хранение Instrument;
сравнение snapshot;
фильтрацию инструментов;
сортировку инструментов;
дедупликацию инструментов;
нормализацию торговых символов;
преобразование Instrument в ExchangeSymbol;
изменение ExchangeService;
изменение AutoTrade;
изменение Telegram UI;
изменение trading runtime;
формирование пользовательских ошибок;
логирование событий.
```

Единственная orchestration-ответственность:

```text
Registry lookup
    ↓
Feed invocation
```

---

## 25. Production composition не входит в Build 012

Build 012 не создаёт автоматически полную production-композицию:

```text
DzengiInstrumentDocumentSource
    +
DzengiInstrumentDocumentHandler
    ↓
InstrumentFeed
    ↓
InstrumentFeedRegistry
    ↓
InstrumentAcquisitionService
```

На текущем этапе новая pipeline остаётся изолированной.

Не создаётся:

```text
global Registry;
global Service;
singleton Feed;
application startup wiring;
dependency container;
ExchangeService integration.
```

Это предотвращает преждевременное изменение production runtime.

---

## 26. Реализованные тестовые сценарии

Создан файл:

```text
app/tests/unit/market_data/acquisition/test_service.py
```

Фактически выполнено:

```text
13 tests
```

Проверены следующие сценарии:

1. загрузка инструментов из зарегистрированного Feed;
2. передача `source_name` Registry без изменения;
3. однократный вызов Registry;
4. однократный вызов Feed;
5. возврат результата без копирования;
6. сохранение порядка инструментов;
7. возврат пустого tuple без ошибки;
8. сохранение Registry error без wrapping;
9. отсутствие вызова Feed при ошибке Registry;
10. сохранение transport error без wrapping;
11. сохранение value error без wrapping;
12. сохранение mapping error без wrapping;
13. отсутствие retry после ошибки Feed.

Часть сценариев реализована через параметризацию.

Фактическое количество выполненных тестовых случаев:

```text
13
```

---

## 27. Проверка получения инструментов

Тест подтверждает:

```python
registry = InstrumentFeedRegistry()

instruments = (
    _instrument(),
)

feed = StubInstrumentFeed(
    instruments=instruments,
)

registry.register(
    "dzengi",
    feed,
)

service = InstrumentAcquisitionService(
    registry=registry,
)

result = service.load_instruments("dzengi")

assert result is instruments
```

Это доказывает:

```text
Service получил Feed через Registry;
Feed был вызван;
результат Feed возвращён;
identity результата сохранена.
```

---

## 28. Проверка передачи source_name без изменения

Используется тестовый Registry, который записывает полученные имена.

Вызов:

```python
service.load_instruments("  dzengi  ")
```

приводит к передаче в Registry именно:

```text
"  dzengi  "
```

Тест подтверждает:

```python
assert registry.requested_source_names == [
    "  dzengi  ",
]
```

Service не дублирует нормализацию Registry.

---

## 29. Проверка отсутствия retry

Тестовый Feed содержит счётчик:

```python
self.load_call_count = 0
```

При вызове:

```python
feed.load_instruments()
```

счётчик увеличивается.

Feed настроен на выбрасывание:

```text
InstrumentReferenceTransportError
```

После вызова Service подтверждено:

```python
assert feed.load_call_count == 1
```

Это доказывает отсутствие скрытой повторной попытки.

---

## 30. Выполненные проверки

### Проверка 1 — unit-тесты Acquisition Service

Команда:

```bash
python -m pytest \
  tests/unit/market_data/acquisition/test_service.py \
  -q
```

Результат:

```text
............. [100%]
13 passed in 0.01s
```

Статус:

```text
PASSED
```

---

### Проверка 2 — Python compilation

Команда:

```bash
python -m py_compile \
  src/market_data/acquisition/service.py \
  tests/unit/market_data/acquisition/test_service.py
```

Результат:

```text
Команда завершилась без ошибок и без вывода.
```

Статус:

```text
PASSED
```

---

### Проверка 3 — полный набор тестов проекта

Команда:

```bash
python -m pytest -q
```

Результат:

```text
.................................................................................................................................................................................... [100%]
180 passed in 0.09s
```

Статус:

```text
PASSED
```

---

### Проверка 4 — отсутствие преждевременной production-интеграции

Команда:

```bash
grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  -E "InstrumentAcquisitionService" \
  src tests
```

Полученное production-определение находится только в:

```text
src/market_data/acquisition/service.py
```

Остальные использования находятся исключительно в:

```text
tests/unit/market_data/acquisition/test_service.py
```

Не обнаружено подключения к:

```text
ExchangeService
Telegram UI
AutoTrade
Trading runtime
другим production-потребителям
```

Статус:

```text
PASSED
```

---

## 31. Архитектура после Build 012

После завершения Build 012 новая часть подсистемы имеет следующую структуру:

```text
market_data/
└── acquisition/
    ├── exceptions.py
    │   ├── MarketDataAcquisitionError
    │   ├── InstrumentReferenceTransportError
    │   ├── InstrumentReferenceSchemaError
    │   ├── InstrumentReferenceParseError
    │   ├── InstrumentReferenceValueError
    │   ├── InstrumentReferenceMappingError
    │   └── InstrumentFeedRegistryError
    │
    ├── protocol.py
    │   ├── InstrumentDocumentSource
    │   ├── InstrumentDocumentHandler
    │   └── InstrumentFeedProtocol
    │
    ├── registry.py
    │   └── InstrumentFeedRegistry
    │
    ├── service.py
    │   └── InstrumentAcquisitionService
    │
    ├── models/
    │   └── instrument.py
    │       └── Instrument
    │
    ├── validation/
    │   ├── schema.py
    │   └── values.py
    │
    ├── handlers/
    │   └── instrument_handler.py
    │       └── DzengiInstrumentDocumentHandler
    │
    ├── feeds/
    │   └── instrument_feed.py
    │       └── InstrumentFeed
    │
    └── adapters/
        └── dzengi/
            ├── models.py
            ├── parser.py
            ├── mapper.py
            └── rest.py
                ├── _PayloadRestClient
                └── DzengiInstrumentDocumentSource
```

---

## 32. Полная архитектурная цепочка после Build 012

После Build 012 реализована полная изолированная acquisition pipeline:

```text
source_name
    ↓
InstrumentAcquisitionService
    ↓
InstrumentFeedRegistry
    ↓
InstrumentFeedProtocol
    ↓
InstrumentFeed
    ↓
InstrumentDocumentSource
    ↓
DzengiInstrumentDocumentSource
    ↓
ExchangeRestClient.get_payload()
    ↓
Dzengi REST API
```

После получения документа:

```text
object
    ↓
InstrumentDocumentHandler
    ↓
DzengiInstrumentDocumentHandler
    ↓
validate_exchange_info_schema()
    ↓
ValidatedExchangeInfoDocument
    ↓
parse_exchange_info()
    ↓
DzengiExchangeInfoResponse
    ↓
validate_exchange_info_values()
    ↓
map_dzengi_exchange_info_to_instruments()
    ↓
tuple[Instrument, ...]
```

Таким образом, техническая цепочка новой acquisition-подсистемы завершена.

---

## 33. Что означает завершение изолированной acquisition pipeline

После Build 012 новая подсистема уже содержит все необходимые уровни:

```text
Internal Model
    ↓
Raw Models
    ↓
Schema Validation
    ↓
Parser
    ↓
Value Validation
    ↓
Mapper
    ↓
Protocols
    ↓
REST Adapter
    ↓
Handler
    ↓
Feed
    ↓
Registry
    ↓
Acquisition Service
```

Но она ещё не заменяет legacy implementation.

Существующий бот продолжает использовать старый путь.

---

## 34. Что ещё не реализовано

После Build 012 отсутствуют:

```text
проверка эквивалентности старой и новой реализации;
compatibility mapper Instrument → ExchangeSymbol;
переключение get_exchange_symbols();
перевод normalize_symbol()/symbol_candidates();
переключение validate_symbol();
переключение get_symbol_runtime_status();
подготовка переноса кэша;
перенос кэша;
перевод production-потребителей;
удаление legacy-кода.
```

Эти задачи относятся к следующим Build.

---

## 35. Влияние на legacy-систему

Build 012 не подключён к существующим компонентам:

```text
ExchangeService
ExchangeSymbol
SymbolValidationResult
Telegram UI
AutoTrade
Market Stream
Market Data Runner
Execution Quality
Trading runtime
```

Не изменены:

```text
ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
normalize_symbol()
symbol_candidates()
```

Старый production-путь продолжает работать без изменений.

---

## 36. Обратная совместимость

Подтверждено сохранение:

```text
сигнатур существующих legacy-методов;
старых импортов;
существующего формата legacy-ошибок;
Telegram UI;
автоторговли;
runtime-поведения;
legacy ExchangeSymbol;
legacy-кэша.
```

Build 012 имеет полную обратную совместимость.

---

## 37. Классификация изменений

| Изменение | Классификация |
|---|---|
| `InstrumentAcquisitionService` | Обязательное архитектурное изменение |
| Registry lookup → Feed invocation | Обязательное архитектурное изменение |
| Явная dependency injection Registry | Обязательное разделение ответственности |
| Передача `source_name` без изменения | Исключение дублирования ответственности |
| Однократный вызов Registry | Предсказуемое orchestration-поведение |
| Однократный вызов Feed | Предсказуемое orchestration-поведение |
| Возврат результата без копирования | Сохранение контракта Feed |
| Сохранение специализированных ошибок | Улучшение диагностируемости |
| Retry | Не выполняется |
| Кэширование | Не выполняется |
| Production composition | Не выполняется |
| Изменение legacy-кода | Отсутствует |
| Изменение production-поведения | Отсутствует |

---

## 38. Условие завершения Build 012

Все условия выполнены:

```text
InstrumentAcquisitionService
    реализован;

Registry
    передаётся как явная зависимость;

source_name
    передаётся Registry без изменения;

Feed
    получается через Registry;

Registry
    вызывается ровно один раз;

Feed
    вызывается ровно один раз;

результат Feed
    возвращается без копирования;

порядок Instrument
    сохраняется;

пустой tuple
    возвращается без ошибки;

специализированные ошибки
    сохраняются без wrapping;

identity ошибки
    сохраняется;

Feed
    не вызывается при ошибке Registry;

retry
    отсутствует;

кэширование
    отсутствует;

прямые Dzengi-зависимости
    отсутствуют;

unit-тесты
    проходят;

полный pytest
    проходит;

production runtime
    не затронут.
```

---

## 39. Итог Build 012

Build 012 завершён успешно.

Реализовано:

```text
InstrumentAcquisitionService

source_name
    ↓
InstrumentFeedRegistry.get()
    ↓
InstrumentFeedProtocol
    ↓
load_instruments()
    ↓
tuple[Instrument, ...]
```

Подтверждено:

```text
13 Acquisition Service tests passed
180 total project tests passed
Python compilation passed
No premature production integration detected
Legacy bot behavior unchanged
```

Итоговый статус:

```text
BUILD 012 — COMPLETE
```

---

## 40. Следующий этап

Следующий этап утверждённого плана:

```text
Build 013 — Проверка эквивалентности старой и новой реализации
```

Его задача — до любого переключения production-кода доказать, что legacy и новая implementation получают эквивалентные данные из одного и того же реального `exchangeInfo`.

Целевая схема:

```text
один реальный exchangeInfo document
    ├──→ legacy implementation
    │       ↓
    │   list[ExchangeSymbol]
    │
    └──→ new acquisition pipeline
            ↓
        tuple[Instrument, ...]

                ↓
        equivalence comparison
```

На Build 013 необходимо определить точный набор полей для сравнения:

```text
symbol;
name;
status;
base_asset;
quote_asset;
asset_type;
market_type;
market_modes;
order_types;
precisions;
tick_size;
tick_value;
step_size;
min_qty;
max_qty;
min_notional;
country;
sector;
industry;
trading_hours.
```

Build 013 не должен:

```text
переключать ExchangeService.get_exchange_symbols();
изменять validate_symbol();
изменять get_symbol_runtime_status();
создавать compatibility mapper;
переносить кэш;
изменять Telegram UI;
изменять AutoTrade;
изменять trading runtime.
```

Только после доказанной эквивалентности можно переходить к:

```text
Build 014 — Compatibility mapper Instrument → ExchangeSymbol
```