build 054: remove legacy klines compatibility API
This commit is contained in:
@@ -21,8 +21,6 @@ from src.integrations.exchange.models import (
|
||||
BalanceSummary,
|
||||
ExchangeHealth,
|
||||
ExecutionPriceSnapshot,
|
||||
Kline,
|
||||
KlineBatch,
|
||||
PrivateAuthHealth,
|
||||
SymbolValidationResult,
|
||||
TimeSyncStatus,
|
||||
@@ -554,59 +552,6 @@ class ExchangeService:
|
||||
)
|
||||
raise ExchangeError(str(exc)) from exc
|
||||
|
||||
# Сохранить legacy KlineBatch как compatibility-фасад над canonical Candle.
|
||||
def get_klines(
|
||||
self,
|
||||
symbol: str | None = None,
|
||||
*,
|
||||
interval: str = "1m",
|
||||
limit: int = 200,
|
||||
price_type: str = "bid",
|
||||
) -> KlineBatch:
|
||||
symbol_to_use = symbol or self.settings.default_symbol
|
||||
|
||||
if not self.settings.exchange_enabled:
|
||||
raise ExchangeError(
|
||||
"Klines are not available in mock exchange mode."
|
||||
)
|
||||
|
||||
if limit <= 0:
|
||||
normalized_limit = 200
|
||||
elif limit > 200:
|
||||
normalized_limit = 200
|
||||
else:
|
||||
normalized_limit = limit
|
||||
|
||||
normalized_price_type = price_type.strip().lower()
|
||||
|
||||
if normalized_price_type not in {"bid", "ask"}:
|
||||
normalized_price_type = "bid"
|
||||
|
||||
canonical_candles = self.get_candles(
|
||||
symbol=symbol_to_use,
|
||||
interval=interval,
|
||||
limit=normalized_limit,
|
||||
price_type=normalized_price_type,
|
||||
)
|
||||
|
||||
candles = [
|
||||
self._kline_from_candle(candle)
|
||||
for candle in canonical_candles[-normalized_limit:]
|
||||
]
|
||||
|
||||
normalized_symbol = (
|
||||
canonical_candles[0].symbol
|
||||
if canonical_candles
|
||||
else normalize_symbol(symbol_to_use)
|
||||
)
|
||||
|
||||
return KlineBatch(
|
||||
symbol=normalized_symbol,
|
||||
interval=interval,
|
||||
candles=candles,
|
||||
source=f"rest_klines:{normalized_price_type}",
|
||||
)
|
||||
|
||||
# Собрать Candles acquisition pipeline и вернуть канонические модели Candle.
|
||||
def _load_candles_via_acquisition(
|
||||
self,
|
||||
@@ -642,20 +587,6 @@ class ExchangeService:
|
||||
price_type=price_type,
|
||||
)
|
||||
|
||||
# Временно преобразовать canonical Candle в legacy Kline.
|
||||
def _kline_from_candle(self, candle: Candle) -> Kline:
|
||||
return Kline(
|
||||
symbol=candle.symbol,
|
||||
interval=candle.interval,
|
||||
open_time=int(candle.open_time.timestamp() * 1000),
|
||||
open_price=float(candle.open_price),
|
||||
high_price=float(candle.high_price),
|
||||
low_price=float(candle.low_price),
|
||||
close_price=float(candle.close_price),
|
||||
volume=float(candle.volume),
|
||||
source=candle.source,
|
||||
)
|
||||
|
||||
# Проверить публичную доступность биржи.
|
||||
def get_health(self) -> ExchangeHealth:
|
||||
if not self.settings.exchange_enabled:
|
||||
|
||||
@@ -1,551 +0,0 @@
|
||||
# app/tests/unit/integrations/exchange/test_service_klines.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
import src.integrations.exchange.service as service_module
|
||||
from src.integrations.exchange.exceptions import ExchangeError
|
||||
from src.integrations.exchange.models import SymbolValidationResult
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.market_data.acquisition.models.candle import Candle
|
||||
|
||||
|
||||
def _candle(
|
||||
*,
|
||||
symbol: str = "BTC/USD_LEVERAGE",
|
||||
interval: str = "1m",
|
||||
open_time_ms: int = 1_750_000_000_000,
|
||||
open_price: str = "100.10",
|
||||
high_price: str = "110.20",
|
||||
low_price: str = "90.30",
|
||||
close_price: str = "105.40",
|
||||
volume: str = "12.50",
|
||||
source: str = "rest_klines:bid",
|
||||
) -> Candle:
|
||||
return Candle(
|
||||
symbol=symbol,
|
||||
interval=interval,
|
||||
open_time=datetime.fromtimestamp(
|
||||
open_time_ms / 1000,
|
||||
tz=timezone.utc,
|
||||
),
|
||||
open_price=Decimal(open_price),
|
||||
high_price=Decimal(high_price),
|
||||
low_price=Decimal(low_price),
|
||||
close_price=Decimal(close_price),
|
||||
volume=Decimal(volume),
|
||||
source=source,
|
||||
)
|
||||
|
||||
|
||||
|
||||
def _set_test_attribute(
|
||||
target: object,
|
||||
name: str,
|
||||
value: object,
|
||||
) -> None:
|
||||
setattr(cast(Any, target), name, value)
|
||||
|
||||
|
||||
def _service(
|
||||
*,
|
||||
default_symbol: str = "BTC/USD_LEVERAGE",
|
||||
exchange_enabled: bool = True,
|
||||
) -> ExchangeService:
|
||||
service = ExchangeService.__new__(ExchangeService)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"settings",
|
||||
SimpleNamespace(
|
||||
default_symbol=default_symbol,
|
||||
exchange_enabled=exchange_enabled,
|
||||
),
|
||||
)
|
||||
return service
|
||||
|
||||
|
||||
def _valid_symbol(
|
||||
symbol: str = "BTC/USD_LEVERAGE",
|
||||
) -> SymbolValidationResult:
|
||||
return SymbolValidationResult(
|
||||
requested_symbol=symbol,
|
||||
normalized_symbol=symbol,
|
||||
is_valid=True,
|
||||
message="OK",
|
||||
symbol_info=None,
|
||||
)
|
||||
|
||||
|
||||
def _invalid_symbol(
|
||||
symbol: str = "UNKNOWN",
|
||||
) -> SymbolValidationResult:
|
||||
return SymbolValidationResult(
|
||||
requested_symbol=symbol,
|
||||
normalized_symbol=symbol,
|
||||
is_valid=False,
|
||||
message="Invalid symbol.",
|
||||
symbol_info=None,
|
||||
)
|
||||
|
||||
|
||||
def test_get_klines_uses_default_symbol() -> None:
|
||||
service = _service(default_symbol="ETH/USD_LEVERAGE")
|
||||
requested_symbols: list[str] = []
|
||||
acquisition_calls: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: (
|
||||
requested_symbols.append(symbol) or _valid_symbol(symbol)
|
||||
),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: acquisition_calls.append(kwargs) or (),
|
||||
)
|
||||
|
||||
result = service.get_klines()
|
||||
|
||||
assert requested_symbols == ["ETH/USD_LEVERAGE"]
|
||||
assert acquisition_calls[0]["symbol"] == "ETH/USD_LEVERAGE"
|
||||
assert result.symbol == "ETH/USD_LEVERAGE"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit", [0, -1, -100])
|
||||
def test_get_klines_normalizes_non_positive_limit(limit: int) -> None:
|
||||
service = _service()
|
||||
captured: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: captured.append(kwargs) or (),
|
||||
)
|
||||
|
||||
service.get_klines(limit=limit)
|
||||
|
||||
assert captured[0]["limit"] == 200
|
||||
|
||||
|
||||
def test_get_klines_caps_limit_at_200() -> None:
|
||||
service = _service()
|
||||
captured: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: captured.append(kwargs) or (),
|
||||
)
|
||||
|
||||
service.get_klines(limit=500)
|
||||
|
||||
assert captured[0]["limit"] == 200
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interval", ["1m", "5m", "15m", "1h"])
|
||||
def test_get_klines_accepts_supported_intervals(interval: str) -> None:
|
||||
service = _service()
|
||||
captured: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: captured.append(kwargs) or (),
|
||||
)
|
||||
|
||||
result = service.get_klines(interval=interval)
|
||||
|
||||
assert captured[0]["interval"] == interval
|
||||
assert result.interval == interval
|
||||
|
||||
|
||||
def test_get_klines_rejects_unsupported_interval() -> None:
|
||||
service = _service()
|
||||
|
||||
with pytest.raises(
|
||||
ExchangeError,
|
||||
match="Unsupported kline interval",
|
||||
):
|
||||
service.get_klines(interval="4h")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("price_type", "expected"),
|
||||
[
|
||||
("bid", "bid"),
|
||||
("ask", "ask"),
|
||||
(" BID ", "bid"),
|
||||
(" AsK ", "ask"),
|
||||
("unknown", "bid"),
|
||||
("", "bid"),
|
||||
],
|
||||
)
|
||||
def test_get_klines_normalizes_price_type(
|
||||
price_type: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
service = _service()
|
||||
captured: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: captured.append(kwargs) or (),
|
||||
)
|
||||
|
||||
result = service.get_klines(price_type=price_type)
|
||||
|
||||
assert captured[0]["price_type"] == expected
|
||||
assert result.source == f"rest_klines:{expected}"
|
||||
|
||||
|
||||
def test_get_klines_rejects_mock_mode() -> None:
|
||||
service = _service(exchange_enabled=False)
|
||||
|
||||
with pytest.raises(
|
||||
ExchangeError,
|
||||
match="Klines are not available in mock exchange mode",
|
||||
):
|
||||
service.get_klines()
|
||||
|
||||
|
||||
def test_get_klines_rejects_invalid_symbol() -> None:
|
||||
service = _service()
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _invalid_symbol(symbol),
|
||||
)
|
||||
|
||||
with pytest.raises(ExchangeError, match="Invalid symbol"):
|
||||
service.get_klines("UNKNOWN")
|
||||
|
||||
|
||||
def test_get_klines_calls_acquisition_with_exact_arguments() -> None:
|
||||
service = _service()
|
||||
captured: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol("BTC/USD_LEVERAGE"),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: captured.append(kwargs) or (),
|
||||
)
|
||||
|
||||
service.get_klines(
|
||||
" btc/usd ",
|
||||
interval="5m",
|
||||
limit=123,
|
||||
price_type="ask",
|
||||
)
|
||||
|
||||
assert captured == [
|
||||
{
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"interval": "5m",
|
||||
"limit": 123,
|
||||
"price_type": "ask",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_get_klines_maps_canonical_candle_to_legacy_kline() -> None:
|
||||
service = _service()
|
||||
candle = _candle()
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: (candle,),
|
||||
)
|
||||
|
||||
result = service.get_klines()
|
||||
|
||||
assert len(result.candles) == 1
|
||||
kline = result.candles[0]
|
||||
assert kline.symbol == candle.symbol
|
||||
assert kline.interval == candle.interval
|
||||
assert kline.open_time == 1_750_000_000_000
|
||||
assert kline.open_price == 100.10
|
||||
assert kline.high_price == 110.20
|
||||
assert kline.low_price == 90.30
|
||||
assert kline.close_price == 105.40
|
||||
assert kline.volume == 12.50
|
||||
assert kline.source == candle.source
|
||||
|
||||
|
||||
def test_get_klines_preserves_candle_order() -> None:
|
||||
service = _service()
|
||||
first = _candle(open_time_ms=1000)
|
||||
second = _candle(open_time_ms=2000)
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: (first, second),
|
||||
)
|
||||
|
||||
result = service.get_klines()
|
||||
|
||||
assert [item.open_time for item in result.candles] == [1000, 2000]
|
||||
|
||||
|
||||
def test_get_klines_trims_result_to_limit() -> None:
|
||||
service = _service()
|
||||
candles = tuple(
|
||||
_candle(open_time_ms=index * 1000)
|
||||
for index in range(1, 6)
|
||||
)
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: candles,
|
||||
)
|
||||
|
||||
result = service.get_klines(limit=2)
|
||||
|
||||
assert [item.open_time for item in result.candles] == [4000, 5000]
|
||||
|
||||
|
||||
def test_get_klines_returns_empty_batch() -> None:
|
||||
service = _service()
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
lambda **kwargs: (),
|
||||
)
|
||||
|
||||
result = service.get_klines()
|
||||
|
||||
assert result.candles == []
|
||||
assert result.source == "rest_klines:bid"
|
||||
|
||||
|
||||
def test_get_klines_logs_and_wraps_acquisition_error() -> None:
|
||||
service = _service()
|
||||
original_error = RuntimeError("candles unavailable")
|
||||
log_calls: list[dict[str, object]] = []
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"validate_symbol",
|
||||
lambda symbol: _valid_symbol(symbol),
|
||||
)
|
||||
|
||||
def raise_error(**kwargs: object) -> tuple[Candle, ...]:
|
||||
raise original_error
|
||||
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_load_candles_via_acquisition",
|
||||
raise_error,
|
||||
)
|
||||
_set_test_attribute(
|
||||
service,
|
||||
"_log_exchange_error",
|
||||
lambda **kwargs: log_calls.append(kwargs),
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
ExchangeError,
|
||||
match="candles unavailable",
|
||||
) as error_info:
|
||||
service.get_klines(
|
||||
interval="15m",
|
||||
limit=25,
|
||||
price_type="ask",
|
||||
)
|
||||
|
||||
assert error_info.value.__cause__ is original_error
|
||||
assert log_calls == [
|
||||
{
|
||||
"endpoint": "klines",
|
||||
"exc": original_error,
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"extra_payload": {
|
||||
"interval": "15m",
|
||||
"limit": 25,
|
||||
"price_type": "ask",
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_kline_from_candle_returns_new_legacy_model() -> None:
|
||||
service = _service()
|
||||
candle = _candle()
|
||||
|
||||
result = service._kline_from_candle(candle)
|
||||
|
||||
assert result is not candle
|
||||
assert result.symbol == candle.symbol
|
||||
assert result.open_time == 1_750_000_000_000
|
||||
|
||||
|
||||
def test_load_candles_via_acquisition_builds_complete_pipeline(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
events: list[tuple[str, object]] = []
|
||||
expected = (_candle(),)
|
||||
|
||||
class FakeSource:
|
||||
def __init__(self) -> None:
|
||||
events.append(("source", self))
|
||||
|
||||
class FakeHandler:
|
||||
def __init__(self) -> None:
|
||||
events.append(("handler", self))
|
||||
|
||||
class FakeFeed:
|
||||
def __init__(self, *, source: object, handler: object) -> None:
|
||||
self.source = source
|
||||
self.handler = handler
|
||||
events.append(("feed", self))
|
||||
|
||||
class FakeRegistry:
|
||||
def __init__(self) -> None:
|
||||
self.registered: tuple[str, object] | None = None
|
||||
events.append(("registry", self))
|
||||
|
||||
def register(self, source_name: str, feed: object) -> None:
|
||||
self.registered = (source_name, feed)
|
||||
events.append(("register", self.registered))
|
||||
|
||||
class FakeAcquisitionService:
|
||||
def __init__(self, *, registry: FakeRegistry) -> None:
|
||||
self.registry = registry
|
||||
events.append(("service", registry))
|
||||
|
||||
def load_candles(
|
||||
self,
|
||||
source_name: str,
|
||||
symbol: str,
|
||||
*,
|
||||
interval: str,
|
||||
limit: int,
|
||||
price_type: str,
|
||||
) -> tuple[Candle, ...]:
|
||||
events.append(
|
||||
(
|
||||
"load",
|
||||
(
|
||||
source_name,
|
||||
symbol,
|
||||
interval,
|
||||
limit,
|
||||
price_type,
|
||||
),
|
||||
)
|
||||
)
|
||||
return expected
|
||||
|
||||
monkeypatch.setattr(
|
||||
service_module,
|
||||
"DzengiCandlesDocumentSource",
|
||||
FakeSource,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
service_module,
|
||||
"DzengiCandlesDocumentHandler",
|
||||
FakeHandler,
|
||||
)
|
||||
monkeypatch.setattr(service_module, "CandlesFeed", FakeFeed)
|
||||
monkeypatch.setattr(
|
||||
service_module,
|
||||
"CandlesFeedRegistry",
|
||||
FakeRegistry,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
service_module,
|
||||
"CandlesAcquisitionService",
|
||||
FakeAcquisitionService,
|
||||
)
|
||||
|
||||
service = _service()
|
||||
result = service._load_candles_via_acquisition(
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
interval="1m",
|
||||
limit=100,
|
||||
price_type="bid",
|
||||
)
|
||||
|
||||
assert result is expected
|
||||
|
||||
source = events[0][1]
|
||||
handler = events[1][1]
|
||||
feed = events[2][1]
|
||||
registry = events[3][1]
|
||||
|
||||
assert isinstance(source, FakeSource)
|
||||
assert isinstance(handler, FakeHandler)
|
||||
assert isinstance(feed, FakeFeed)
|
||||
assert isinstance(registry, FakeRegistry)
|
||||
|
||||
assert feed.source is source
|
||||
assert feed.handler is handler
|
||||
assert registry.registered == ("dzengi", feed)
|
||||
|
||||
assert (
|
||||
"load",
|
||||
(
|
||||
"dzengi",
|
||||
"BTC/USD_LEVERAGE",
|
||||
"1m",
|
||||
100,
|
||||
"bid",
|
||||
),
|
||||
) in events
|
||||
355
docs/migrations/build_054.md
Normal file
355
docs/migrations/build_054.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# Build 054 — Удаление legacy API get_klines и compatibility bridge
|
||||
|
||||
## Статус
|
||||
|
||||
**Завершён**
|
||||
|
||||
---
|
||||
|
||||
## Цель Build
|
||||
|
||||
Удалить больше не используемый compatibility API legacy OHLCV:
|
||||
|
||||
```text
|
||||
ExchangeService.get_klines()
|
||||
```
|
||||
|
||||
а также внутренний bridge:
|
||||
|
||||
```text
|
||||
ExchangeService._kline_from_candle()
|
||||
```
|
||||
|
||||
и связанный test-only файл:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_klines.py
|
||||
```
|
||||
|
||||
Build 054 не удаляет сами модели:
|
||||
|
||||
```text
|
||||
Kline
|
||||
KlineBatch
|
||||
```
|
||||
|
||||
Они сохраняются до отдельного Build после повторной глобальной проверки зависимостей.
|
||||
|
||||
---
|
||||
|
||||
## Исходное состояние
|
||||
|
||||
По результатам Build 052 было подтверждено:
|
||||
|
||||
- production-вызовы `ExchangeService.get_klines()` отсутствуют;
|
||||
- `get_klines()` используется только тестами;
|
||||
- `_kline_from_candle()` используется только внутри `get_klines()` и его тестов;
|
||||
- `Kline` и `KlineBatch` остаются только внутри compatibility-слоя;
|
||||
- активный Market Analysis полностью использует `ExchangeService.get_candles()`.
|
||||
|
||||
После Build 053 также был удалён последний неиспользуемый production-файл:
|
||||
|
||||
```text
|
||||
src/trading/market_analysis/indicators_legacy.py
|
||||
```
|
||||
|
||||
Таким образом compatibility API больше не обслуживал ни одного рабочего потребителя.
|
||||
|
||||
---
|
||||
|
||||
## Объём изменений
|
||||
|
||||
В Build 054 изменён:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/service.py
|
||||
```
|
||||
|
||||
Удалён:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_klines.py
|
||||
```
|
||||
|
||||
Добавлена документация:
|
||||
|
||||
```text
|
||||
docs/migrations/build_054.md
|
||||
```
|
||||
|
||||
Build 054 не изменяет:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/models.py
|
||||
src/market_data/acquisition/
|
||||
src/trading/market_analysis/
|
||||
```
|
||||
|
||||
Build также не удаляет:
|
||||
|
||||
```text
|
||||
Kline
|
||||
KlineBatch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Изменения в ExchangeService
|
||||
|
||||
Из файла:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/service.py
|
||||
```
|
||||
|
||||
удалены импорты:
|
||||
|
||||
```text
|
||||
Kline
|
||||
KlineBatch
|
||||
```
|
||||
|
||||
Удалён публичный legacy-метод:
|
||||
|
||||
```text
|
||||
ExchangeService.get_klines()
|
||||
```
|
||||
|
||||
Удалён внутренний compatibility helper:
|
||||
|
||||
```text
|
||||
ExchangeService._kline_from_candle()
|
||||
```
|
||||
|
||||
Остальной код `ExchangeService` сохранён без изменений.
|
||||
|
||||
---
|
||||
|
||||
## Удаление test-only compatibility coverage
|
||||
|
||||
Удалён файл:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_klines.py
|
||||
```
|
||||
|
||||
Он полностью покрывал уже удалённые компоненты:
|
||||
|
||||
```text
|
||||
get_klines()
|
||||
_kline_from_candle()
|
||||
Candle → Kline
|
||||
KlineBatch
|
||||
```
|
||||
|
||||
Новый канонический API продолжает покрываться файлом:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_candles.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Архитектурный результат
|
||||
|
||||
До Build 054:
|
||||
|
||||
```text
|
||||
ExchangeService.get_klines()
|
||||
↓
|
||||
ExchangeService.get_candles()
|
||||
↓
|
||||
Candle
|
||||
↓
|
||||
_kline_from_candle()
|
||||
↓
|
||||
Kline
|
||||
↓
|
||||
KlineBatch
|
||||
```
|
||||
|
||||
После Build 054:
|
||||
|
||||
```text
|
||||
ExchangeService.get_candles()
|
||||
↓
|
||||
tuple[Candle, ...]
|
||||
```
|
||||
|
||||
Compatibility bridge полностью удалён.
|
||||
|
||||
---
|
||||
|
||||
## Полный regression suite
|
||||
|
||||
После удаления выполнена команда:
|
||||
|
||||
```bash
|
||||
python -m pytest -q
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
779 passed in 2.66s
|
||||
```
|
||||
|
||||
Снижение количества тестов связано с удалением test-only файла:
|
||||
|
||||
```text
|
||||
tests/unit/integrations/exchange/test_service_klines.py
|
||||
```
|
||||
|
||||
Регрессий не обнаружено.
|
||||
|
||||
---
|
||||
|
||||
## Контроль отсутствия get_klines
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
grep -RIn \
|
||||
--exclude-dir="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
"\.get_klines(" \
|
||||
src tests
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
пусто
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Контроль отсутствия _kline_from_candle
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
grep -RIn \
|
||||
--exclude-dir="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
"_kline_from_candle" \
|
||||
src tests
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
пусто
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Контроль оставшихся Kline и KlineBatch
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
grep -RIn \
|
||||
--exclude-dir="__pycache__" \
|
||||
--exclude="*.pyc" \
|
||||
-E "\bKlineBatch\b|\bKline\b" \
|
||||
src tests
|
||||
```
|
||||
|
||||
Результат:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/models.py:93:class Kline:
|
||||
src/integrations/exchange/models.py:110:class KlineBatch:
|
||||
src/integrations/exchange/models.py:113: candles: list[Kline]
|
||||
```
|
||||
|
||||
Следовательно, после Build 054 `Kline` и `KlineBatch` существуют только как неиспользуемые определения моделей.
|
||||
|
||||
---
|
||||
|
||||
## Проверка форматирования
|
||||
|
||||
Выполнена команда:
|
||||
|
||||
```bash
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Вывод отсутствует.
|
||||
|
||||
Whitespace-ошибок не обнаружено.
|
||||
|
||||
---
|
||||
|
||||
## Фактический diff
|
||||
|
||||
Перед добавлением документации:
|
||||
|
||||
```text
|
||||
app/src/integrations/exchange/service.py | 69 ------------------
|
||||
app/tests/unit/integrations/exchange/test_service_klines.py | 551 -----------------------------------------------------------------------------------------------------------------------------------------------
|
||||
2 files changed, 620 deletions(-)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Что намеренно не выполнено
|
||||
|
||||
Build 054 намеренно не включает:
|
||||
|
||||
- удаление `Kline`;
|
||||
- удаление `KlineBatch`;
|
||||
- изменение `src/integrations/exchange/models.py`;
|
||||
- изменение `ExchangeService.get_candles()`;
|
||||
- изменение Market Data Acquisition;
|
||||
- изменение Market Analysis;
|
||||
- изменение торговой логики;
|
||||
- изменение структуры каталогов.
|
||||
|
||||
---
|
||||
|
||||
## Критерии завершения
|
||||
|
||||
Build 054 считается завершённым, поскольку:
|
||||
|
||||
- production-потребители `get_klines()` отсутствовали до удаления;
|
||||
- `get_klines()` удалён;
|
||||
- `_kline_from_candle()` удалён;
|
||||
- test-only compatibility coverage удалено;
|
||||
- полный suite проходит;
|
||||
- grep по `get_klines()` пустой;
|
||||
- grep по `_kline_from_candle()` пустой;
|
||||
- `Kline` и `KlineBatch` остались только в `models.py`;
|
||||
- `git diff --check` чистый.
|
||||
|
||||
---
|
||||
|
||||
## Итог
|
||||
|
||||
**Build 054 завершён успешно.**
|
||||
|
||||
Текущее состояние:
|
||||
|
||||
```text
|
||||
get_klines — удалён
|
||||
_kline_from_candle — удалён
|
||||
test_service_klines.py — удалён
|
||||
Full test suite — 779 passed
|
||||
Kline/KlineBatch — только неиспользуемые определения в models.py
|
||||
```
|
||||
|
||||
Следующий этап — удалить модели:
|
||||
|
||||
```text
|
||||
Kline
|
||||
KlineBatch
|
||||
```
|
||||
|
||||
из:
|
||||
|
||||
```text
|
||||
src/integrations/exchange/models.py
|
||||
```
|
||||
|
||||
после повторной глобальной проверки.
|
||||
Reference in New Issue
Block a user