build 039: complete Quotes Feed migration foundation
This commit is contained in:
83
app/tests/unit/trading/auto/test_execution_quality.py
Normal file
83
app/tests/unit/trading/auto/test_execution_quality.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# app/tests/unit/trading/auto/test_execution_quality.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import src.trading.auto.execution_quality as module
|
||||
from src.integrations.exchange.models import ExecutionPriceSnapshot
|
||||
from src.trading.auto.execution_quality import AutoExecutionQualityMixin
|
||||
|
||||
|
||||
class Harness(AutoExecutionQualityMixin):
|
||||
_spread_thresholds_by_asset = {}
|
||||
_default_spread_thresholds = {
|
||||
"warning_enter": 1.0,
|
||||
"warning_exit": 0.8,
|
||||
"block_enter": 2.0,
|
||||
"block_exit": 1.5,
|
||||
}
|
||||
_max_snapshot_age_seconds = 5.0
|
||||
_warning_snapshot_age_seconds = 2.0
|
||||
_last_logged_execution_quality_key = None
|
||||
|
||||
def _log_execution_quality_if_changed(self, **_: object) -> None:
|
||||
return None
|
||||
|
||||
def _apply_exchange_block_state(self, **_: object) -> None:
|
||||
raise AssertionError("exchange block must not be applied")
|
||||
|
||||
|
||||
def _state() -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
market_is_open=True,
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
strategy="trend",
|
||||
status="RUNNING",
|
||||
|
||||
execution_quality=None,
|
||||
execution_quality_reason=None,
|
||||
execution_quality_message=None,
|
||||
execution_block_reason=None,
|
||||
|
||||
market_runtime_degraded=False,
|
||||
snapshot_age_seconds=None,
|
||||
spread_percent=None,
|
||||
|
||||
execution_price_age_seconds=None,
|
||||
execution_bid_price=None,
|
||||
execution_ask_price=None,
|
||||
execution_last_price=None,
|
||||
execution_price_freshness=None,
|
||||
)
|
||||
|
||||
def test_execution_quality_uses_typed_execution_snapshot(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
snapshot = ExecutionPriceSnapshot(
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
last_price=100.5,
|
||||
bid_price=100.0,
|
||||
ask_price=101.0,
|
||||
updated_at="13.07.2026 15:00:00",
|
||||
source="dzengi:fresh_cache",
|
||||
is_fresh=True,
|
||||
age_seconds=0.5,
|
||||
)
|
||||
|
||||
class Service:
|
||||
def get_execution_snapshot(self, *_: object, **__: object) -> ExecutionPriceSnapshot:
|
||||
return snapshot
|
||||
|
||||
monkeypatch.setattr(module, "ExchangeService", Service)
|
||||
|
||||
state = _state()
|
||||
Harness()._sync_execution_quality_state(state) # type: ignore[arg-type]
|
||||
|
||||
assert state.execution_bid_price == 100.0
|
||||
assert state.execution_ask_price == 101.0
|
||||
assert state.execution_last_price == 100.5
|
||||
assert state.execution_price_source == "dzengi:fresh_cache"
|
||||
assert state.snapshot_age_seconds == 0.5
|
||||
75
app/tests/unit/trading/auto/test_signal_runtime_quote.py
Normal file
75
app/tests/unit/trading/auto/test_signal_runtime_quote.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# app/tests/unit/trading/auto/test_signal_runtime_quote.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
import src.trading.auto.signal_runtime as module
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.trading.auto.signal_runtime import AutoSignalRuntimeMixin
|
||||
|
||||
|
||||
def _quote() -> Quote:
|
||||
return Quote(
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
last_price=Decimal("100.5"),
|
||||
bid_price=Decimal("100.0"),
|
||||
ask_price=Decimal("101.0"),
|
||||
exchange_timestamp=None,
|
||||
received_at=datetime.now(timezone.utc),
|
||||
source="dzengi",
|
||||
)
|
||||
|
||||
|
||||
class Harness(AutoSignalRuntimeMixin):
|
||||
_ready_confidence = 0.3
|
||||
|
||||
|
||||
def test_ready_signal_uses_canonical_quote(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
quote = _quote()
|
||||
requested: list[tuple[str, str]] = []
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
class Service:
|
||||
def get_quote(self, symbol: str, *, runtime_key: str) -> Quote:
|
||||
requested.append((symbol, runtime_key))
|
||||
return quote
|
||||
|
||||
class Journal:
|
||||
def log_ui_info(self, **kwargs: object) -> None:
|
||||
captured.update(kwargs)
|
||||
|
||||
harness = Harness()
|
||||
|
||||
def build_payload(**kwargs: object) -> dict[str, object]:
|
||||
captured["quote"] = kwargs["quote"]
|
||||
return {"ok": True}
|
||||
|
||||
monkeypatch.setattr(module, "ExchangeService", Service)
|
||||
monkeypatch.setattr(module, "JournalService", Journal)
|
||||
monkeypatch.setattr(harness, "_build_ready_signal_payload", build_payload)
|
||||
|
||||
state = SimpleNamespace(symbol="BTC/USD_LEVERAGE")
|
||||
harness._log_ready_signal(
|
||||
state=state, # type: ignore[arg-type]
|
||||
signal="BUY",
|
||||
reason="test",
|
||||
confidence=0.9,
|
||||
signal_intent="ENTRY",
|
||||
)
|
||||
|
||||
assert requested == [("BTC/USD_LEVERAGE", "auto")]
|
||||
assert captured["quote"] is quote
|
||||
|
||||
|
||||
def test_signal_runtime_has_no_legacy_market_snapshot_call() -> None:
|
||||
source = inspect.getsource(module.AutoSignalRuntimeMixin)
|
||||
|
||||
assert "get_quote(" in source
|
||||
49
app/tests/unit/trading/debug/test_execution.py
Normal file
49
app/tests/unit/trading/debug/test_execution.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# app/tests/unit/trading/debug/test_execution.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import src.trading.debug.execution as module
|
||||
from src.integrations.exchange.models import ExecutionPriceSnapshot
|
||||
from src.trading.debug.execution import DebugExecutionEngine
|
||||
|
||||
|
||||
def _snapshot() -> ExecutionPriceSnapshot:
|
||||
return ExecutionPriceSnapshot(
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
last_price=100.5,
|
||||
bid_price=100.0,
|
||||
ask_price=101.0,
|
||||
updated_at="13.07.2026 15:00:00",
|
||||
source="rest_fallback",
|
||||
is_fresh=True,
|
||||
age_seconds=0.0,
|
||||
)
|
||||
|
||||
|
||||
def test_debug_execution_uses_execution_snapshot(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
calls: list[tuple[str, str | None]] = []
|
||||
|
||||
class Service:
|
||||
def get_execution_snapshot(
|
||||
self,
|
||||
symbol: str,
|
||||
*,
|
||||
runtime_key: str | None = None,
|
||||
) -> ExecutionPriceSnapshot:
|
||||
calls.append((symbol, runtime_key))
|
||||
return _snapshot()
|
||||
|
||||
monkeypatch.setattr(module, "ExchangeService", Service)
|
||||
|
||||
engine = DebugExecutionEngine()
|
||||
|
||||
assert engine._entry_price_for_side("BTC/USD_LEVERAGE", "LONG") == 101.0
|
||||
assert engine._entry_price_for_side("BTC/USD_LEVERAGE", "SHORT") == 100.0
|
||||
assert engine._exit_price_for_side("BTC/USD_LEVERAGE", "LONG") == 100.0
|
||||
assert engine._exit_price_for_side("BTC/USD_LEVERAGE", "SHORT") == 101.0
|
||||
assert engine._market_last_price("BTC/USD_LEVERAGE") == 100.5
|
||||
assert calls == [("BTC/USD_LEVERAGE", "debug_auto")] * 5
|
||||
52
app/tests/unit/trading/strategies/test_scalp_quote.py
Normal file
52
app/tests/unit/trading/strategies/test_scalp_quote.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# app/tests/unit/trading/strategies/test_scalp_quote.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
|
||||
import src.trading.strategies.scalp as module
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.trading.strategies.scalp import ScalpStrategy
|
||||
|
||||
|
||||
def _quote(
|
||||
*,
|
||||
last: str = "100.5",
|
||||
bid: str = "100.0",
|
||||
ask: str = "101.0",
|
||||
) -> Quote:
|
||||
return Quote(
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
last_price=Decimal(last),
|
||||
bid_price=Decimal(bid),
|
||||
ask_price=Decimal(ask),
|
||||
exchange_timestamp=None,
|
||||
received_at=datetime.now(timezone.utc),
|
||||
source="dzengi",
|
||||
)
|
||||
|
||||
|
||||
def test_scalp_uses_midpoint_from_quote() -> None:
|
||||
result = ScalpStrategy()._analysis_price(_quote())
|
||||
|
||||
assert result == 100.5
|
||||
|
||||
|
||||
def test_scalp_quote_snapshot_is_json_compatible_projection() -> None:
|
||||
result = ScalpStrategy()._quote_snapshot(_quote())
|
||||
|
||||
assert result == {
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"last_price": 100.5,
|
||||
"bid_price": 100.0,
|
||||
"ask_price": 101.0,
|
||||
"source": "dzengi",
|
||||
}
|
||||
|
||||
|
||||
def test_scalp_has_no_legacy_market_snapshot_call() -> None:
|
||||
source = inspect.getsource(module.ScalpStrategy)
|
||||
|
||||
assert "get_quote(" in source
|
||||
52
app/tests/unit/trading/strategies/test_trend_quote.py
Normal file
52
app/tests/unit/trading/strategies/test_trend_quote.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# app/tests/unit/trading/strategies/test_trend_quote.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
|
||||
import src.trading.strategies.trend as module
|
||||
from src.market_data.acquisition.models.quote import Quote
|
||||
from src.trading.strategies.trend import TrendStrategy
|
||||
|
||||
|
||||
def _quote(
|
||||
*,
|
||||
last: str = "100.5",
|
||||
bid: str = "100.0",
|
||||
ask: str = "101.0",
|
||||
) -> Quote:
|
||||
return Quote(
|
||||
symbol="BTC/USD_LEVERAGE",
|
||||
last_price=Decimal(last),
|
||||
bid_price=Decimal(bid),
|
||||
ask_price=Decimal(ask),
|
||||
exchange_timestamp=None,
|
||||
received_at=datetime.now(timezone.utc),
|
||||
source="dzengi",
|
||||
)
|
||||
|
||||
|
||||
def test_trend_uses_midpoint_from_quote() -> None:
|
||||
result = TrendStrategy()._analysis_price(_quote())
|
||||
|
||||
assert result == 100.5
|
||||
|
||||
|
||||
def test_trend_quote_snapshot_is_json_compatible_projection() -> None:
|
||||
result = TrendStrategy()._quote_snapshot(_quote())
|
||||
|
||||
assert result == {
|
||||
"symbol": "BTC/USD_LEVERAGE",
|
||||
"last_price": 100.5,
|
||||
"bid_price": 100.0,
|
||||
"ask_price": 101.0,
|
||||
"source": "dzengi",
|
||||
}
|
||||
|
||||
|
||||
def test_trend_has_no_legacy_market_snapshot_call() -> None:
|
||||
source = inspect.getsource(module.TrendStrategy)
|
||||
|
||||
assert "get_quote(" in source
|
||||
Reference in New Issue
Block a user