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.
This commit is contained in:
221
nautilus_dolphin/tests/test_live_price_feed.py
Executable file
221
nautilus_dolphin/tests/test_live_price_feed.py
Executable file
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user