Files
DOLPHIN/nautilus_dolphin/tests/test_live_price_feed.py
hjnormey 01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
2026-04-21 16:58:38 +02:00

222 lines
7.3 KiB
Python
Executable File

"""
test_live_price_feed.py — Unit tests for live_price_feed module.
Tests cover all three implementations (NullPriceFeed, ConstantPriceFeed,
NautilusCachePriceFeed) and verify Protocol structural conformance.
"""
import sys
import unittest
from unittest.mock import MagicMock
sys.path.insert(0, 'nautilus_dolphin')
from nautilus_dolphin.nautilus.live_price_feed import (
PriceFeed,
NullPriceFeed,
ConstantPriceFeed,
NautilusCachePriceFeed,
)
class TestNullPriceFeed(unittest.TestCase):
def setUp(self):
self.feed = NullPriceFeed()
def test_bid_returns_none(self):
self.assertIsNone(self.feed.bid('BTCUSDT'))
def test_ask_returns_none(self):
self.assertIsNone(self.feed.ask('BTCUSDT'))
def test_mid_returns_none(self):
self.assertIsNone(self.feed.mid('BTCUSDT'))
def test_last_update_ns_returns_zero(self):
self.assertEqual(self.feed.last_update_ns('BTCUSDT'), 0)
def test_unknown_symbol_returns_none(self):
self.assertIsNone(self.feed.bid('UNKNOWNSYM'))
def test_satisfies_price_feed_protocol(self):
self.assertIsInstance(self.feed, PriceFeed)
class TestConstantPriceFeed(unittest.TestCase):
def setUp(self):
self.feed = ConstantPriceFeed({
'BTCUSDT': (82000.0, 82010.0),
'ETHUSDT': (1595.5, 1596.0),
})
def test_bid_known_symbol(self):
self.assertAlmostEqual(self.feed.bid('BTCUSDT'), 82000.0)
def test_ask_known_symbol(self):
self.assertAlmostEqual(self.feed.ask('BTCUSDT'), 82010.0)
def test_mid_known_symbol(self):
expected = (82000.0 + 82010.0) / 2.0
self.assertAlmostEqual(self.feed.mid('BTCUSDT'), expected)
def test_bid_second_symbol(self):
self.assertAlmostEqual(self.feed.bid('ETHUSDT'), 1595.5)
def test_ask_second_symbol(self):
self.assertAlmostEqual(self.feed.ask('ETHUSDT'), 1596.0)
def test_unknown_symbol_bid_returns_none(self):
self.assertIsNone(self.feed.bid('SOLUSDT'))
def test_unknown_symbol_ask_returns_none(self):
self.assertIsNone(self.feed.ask('SOLUSDT'))
def test_unknown_symbol_mid_returns_none(self):
self.assertIsNone(self.feed.mid('SOLUSDT'))
def test_last_update_ns_returns_zero(self):
self.assertEqual(self.feed.last_update_ns('BTCUSDT'), 0)
def test_update_changes_price(self):
self.feed.update('BTCUSDT', 81000.0, 81010.0)
self.assertAlmostEqual(self.feed.bid('BTCUSDT'), 81000.0)
self.assertAlmostEqual(self.feed.ask('BTCUSDT'), 81010.0)
def test_update_new_symbol(self):
self.feed.update('SOLUSDT', 120.0, 120.05)
self.assertAlmostEqual(self.feed.bid('SOLUSDT'), 120.0)
def test_remove_symbol(self):
self.feed.remove('BTCUSDT')
self.assertIsNone(self.feed.bid('BTCUSDT'))
def test_remove_unknown_is_noop(self):
# Should not raise
self.feed.remove('NONEXISTENT')
def test_empty_feed_construction(self):
feed = ConstantPriceFeed()
self.assertIsNone(feed.bid('BTCUSDT'))
def test_satisfies_price_feed_protocol(self):
self.assertIsInstance(self.feed, PriceFeed)
class TestNautilusCachePriceFeed(unittest.TestCase):
def _make_quote_tick(self, bid: float, ask: float, ts_event: int = 1234567890_000_000_000):
qt = MagicMock()
qt.bid_price = bid
qt.ask_price = ask
qt.ts_event = ts_event
return qt
def _make_cache(self, quote_ticks: dict):
"""
Build a mock NT cache where quote_tick(iid) returns the given tick
if iid.value is in quote_ticks, else None.
"""
cache = MagicMock()
def _quote_tick(iid):
return quote_ticks.get(str(iid))
cache.quote_tick.side_effect = _quote_tick
return cache
def _make_iid(self, symbol_venue: str):
"""Return a mock InstrumentId whose str() is symbol_venue."""
iid = MagicMock()
iid.__str__ = lambda self: symbol_venue
return iid
def setUp(self):
btc_tick = self._make_quote_tick(82000.0, 82010.0, ts_event=9_999_999)
eth_tick = self._make_quote_tick(1595.5, 1596.0, ts_event=8_888_888)
self.cache = self._make_cache({
'BTCUSDT.BINANCE': btc_tick,
'ETHUSDT.BINANCE': eth_tick,
})
self.feed = NautilusCachePriceFeed(self.cache, venue='BINANCE')
def _patch_iid(self, feed, symbol, venue):
"""Pre-populate the IID cache so the mock cache key matches."""
iid = MagicMock()
iid.__str__ = lambda s: f'{symbol}.{venue}'
feed._iid_cache[symbol] = iid
def test_bid_returns_float(self):
self._patch_iid(self.feed, 'BTCUSDT', 'BINANCE')
self.assertAlmostEqual(self.feed.bid('BTCUSDT'), 82000.0)
def test_ask_returns_float(self):
self._patch_iid(self.feed, 'BTCUSDT', 'BINANCE')
self.assertAlmostEqual(self.feed.ask('BTCUSDT'), 82010.0)
def test_mid_is_average(self):
self._patch_iid(self.feed, 'BTCUSDT', 'BINANCE')
expected = (82000.0 + 82010.0) / 2.0
self.assertAlmostEqual(self.feed.mid('BTCUSDT'), expected)
def test_no_quote_returns_none(self):
self._patch_iid(self.feed, 'SOLUSDT', 'BINANCE')
self.assertIsNone(self.feed.bid('SOLUSDT'))
self.assertIsNone(self.feed.ask('SOLUSDT'))
self.assertIsNone(self.feed.mid('SOLUSDT'))
def test_last_update_ns_returns_ts_event(self):
self._patch_iid(self.feed, 'BTCUSDT', 'BINANCE')
self.assertEqual(self.feed.last_update_ns('BTCUSDT'), 9_999_999)
def test_last_update_ns_no_quote_returns_zero(self):
self._patch_iid(self.feed, 'SOLUSDT', 'BINANCE')
self.assertEqual(self.feed.last_update_ns('SOLUSDT'), 0)
def test_iid_cache_populated_on_first_call(self):
"""IID cache should store parsed InstrumentId after first access."""
# Pre-patch so mock cache resolves correctly
self._patch_iid(self.feed, 'ETHUSDT', 'BINANCE')
_ = self.feed.bid('ETHUSDT')
self.assertIn('ETHUSDT', self.feed._iid_cache)
def test_satisfies_price_feed_protocol(self):
self.assertIsInstance(self.feed, PriceFeed)
class TestPriceFeedProtocol(unittest.TestCase):
"""Verify all implementations satisfy the PriceFeed Protocol at runtime."""
def test_null_is_price_feed(self):
self.assertIsInstance(NullPriceFeed(), PriceFeed)
def test_constant_is_price_feed(self):
self.assertIsInstance(ConstantPriceFeed(), PriceFeed)
def test_nautilus_is_price_feed(self):
feed = NautilusCachePriceFeed(MagicMock())
self.assertIsInstance(feed, PriceFeed)
def test_custom_class_satisfying_protocol(self):
"""Any class with the right methods satisfies PriceFeed structurally."""
class MinimalFeed:
def bid(self, s): return 1.0
def ask(self, s): return 1.0
def mid(self, s): return 1.0
def last_update_ns(self, s): return 0
self.assertIsInstance(MinimalFeed(), PriceFeed)
def test_incomplete_class_does_not_satisfy_protocol(self):
"""A class missing methods should NOT satisfy PriceFeed."""
class IncompleteFeed:
def bid(self, s): return 1.0
# missing ask, mid, last_update_ns
self.assertNotIsInstance(IncompleteFeed(), PriceFeed)
if __name__ == '__main__':
unittest.main()