Stage 03.2 - market timestamp in local timezone

This commit is contained in:
2026-04-13 23:54:21 +03:00
parent 9166022b3c
commit d2d024f39b
3 changed files with 97 additions and 7 deletions

View File

@@ -1,12 +1,18 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime
from zoneinfo import ZoneInfo
from src.core.config import load_settings from src.core.config import load_settings
from src.core.config import load_settings
from src.integrations.exchange.exceptions import ExchangeError
from src.integrations.exchange.mock_data import ( from src.integrations.exchange.mock_data import (
mock_balance_summary, mock_balance_summary,
mock_exchange_health, mock_exchange_health,
mock_ticker_price, mock_ticker_price,
) )
from src.integrations.exchange.models import BalanceSummary, ExchangeHealth, TickerPrice from src.integrations.exchange.models import BalanceSummary, ExchangeHealth, TickerPrice
from src.integrations.exchange.rest_client import ExchangeRestClient
class ExchangeService: class ExchangeService:
@@ -17,22 +23,57 @@ class ExchangeService:
if not self.settings.exchange_enabled: if not self.settings.exchange_enabled:
return mock_exchange_health() return mock_exchange_health()
if not self.settings.exchange_api_key or not self.settings.exchange_api_secret: try:
ticker = self._get_real_price(self.settings.default_symbol)
except ExchangeError as exc:
return ExchangeHealth( return ExchangeHealth(
ok=False, ok=False,
mode="configured_without_keys", mode="real_error",
message="Интеграция включена, но API key / secret не заданы.", message=f"Ошибка подключения к API: {exc}",
) )
return ExchangeHealth( return ExchangeHealth(
ok=False, ok=True,
mode="real_pending", mode="real_public_api",
message="Реальный REST client еще не подключен. Пока доступен только mock mode.", message=f"Public API OK. Последняя цена {ticker.symbol}: {ticker.price:.2f}",
) )
def get_price(self, symbol: str | None = None) -> TickerPrice: def get_price(self, symbol: str | None = None) -> TickerPrice:
symbol_to_use = symbol or self.settings.default_symbol symbol_to_use = symbol or self.settings.default_symbol
return mock_ticker_price(symbol_to_use)
if not self.settings.exchange_enabled:
return mock_ticker_price(symbol_to_use)
return self._get_real_price(symbol_to_use)
def get_balance_summary(self) -> list[BalanceSummary]: def get_balance_summary(self) -> list[BalanceSummary]:
return mock_balance_summary() return mock_balance_summary()
def _get_real_price(self, symbol: str) -> TickerPrice:
client = ExchangeRestClient()
payload = client.get_json(
"/api/v2/ticker/24hr",
params={"symbol": symbol},
)
price_raw = payload.get("lastPrice")
if price_raw is None:
raise ExchangeError("Field 'lastPrice' is missing in ticker response.")
close_time = payload.get("closeTime") or payload.get("eventTime") or ""
settings = load_settings()
if close_time:
dt_utc = datetime.fromtimestamp(int(close_time) / 1000, tz=ZoneInfo("UTC"))
dt_local = dt_utc.astimezone(ZoneInfo(settings.tz))
updated_at = dt_local.strftime("%d/%m/%Y %H:%M:%S")
else:
updated_at = "n/a"
return TickerPrice(
symbol=symbol,
price=float(price_raw),
source="dzengi-demo-api",
updated_at=updated_at,
)

View File

@@ -0,0 +1,15 @@
# 0006 — Public REST Client First
## Решение
Подключать первую реальную интеграцию с биржей через public readonly endpoint, а не через private auth и не через ордера.
## Причины
- это безопаснее
- это быстрее дает полезный результат в UI
- это позволяет стабилизировать transport / error handling
- это не требует сразу заводить реальные торговые операции
## Последствия
- рынок получает реальные данные раньше, чем private account functions
- system screen начинает показывать реальный API health
- архитектура интеграции остается чистой

View File

@@ -0,0 +1,34 @@
# Stage 03.2 — Public REST client
## Цель
Перевести экран `📈 Рынок` с mock-цены на реальную публичную цену из Dzengi demo API.
## Что добавляется
- `exceptions.py`
- `rest_client.py`
- real path в `service.py`
- обновленный `market.py`
- обновленный `system_status.py`
## Что используется
- Base demo URL: `https://demo-api-adapter.dzengi.com`
- Endpoint: `GET /api/v2/ticker/24hr`
- Symbol: `BTC/USD_LEVERAGE`
## Как работает
### Если `EXCHANGE_ENABLED=false`
- используется mock mode
### Если `EXCHANGE_ENABLED=true`
- выполняется реальный public GET request
- `📈 Рынок` показывает реальную цену
- `⚙️ Система` показывает статус API
## Что пока НЕ делается
- private auth
- баланс через реальные ключи
- создание ордеров
- websocket
- retry logic
- backoff
- rate-limit handling