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.
222 lines
7.3 KiB
Python
Executable File
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()
|