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:
hjnormey
2026-04-21 16:58:38 +02:00
commit 01c19662cb
643 changed files with 260241 additions and 0 deletions

392
prod/test_deribit_fix.py Executable file
View File

@@ -0,0 +1,392 @@
#!/usr/bin/env python3
"""
DERIBIT API FIX - UNIT TESTS WITH CONTROL VALUES
================================================
Control Values are historical known-good data points for validation.
=== MARCH 2026 CONTROL VALUES (from 2026-03-19 12:00 UTC) ===
- DVOL BTC: 54.88
- DVOL ETH: 79.13
- Funding BTC: 2.7150135764992048e-06
- Funding ETH: -8.348741006833234e-07
=== MID-FEBRUARY 2026 CONTROL VALUES (from 2026-02-15 12:00 UTC) ===
- Funding BTC: 0.0001770952404538274 (17.7 bps - high funding period)
- Funding ETH: -4.402918249409541e-06 (-0.44 bps - negative funding)
Note: DVOL historical data has limited retention. Tests use March 2026
as primary control, with range validation for current values.
=== CURRENT VALID RANGES ===
- DVOL BTC: 30.0 - 150.0 (typical BTC volatility range)
- DVOL ETH: 40.0 - 200.0 (ETH typically higher vol than BTC)
- Funding: -0.001 to +0.001 (-100 bps to +100 bps)
These tests verify:
1. URL builder generates correct timestamps
2. Parsers extract correct values from API response format
3. Values match known control values within tolerance
4. Current values fall within expected ranges
"""
import unittest
import json
import time
from datetime import datetime, timezone
import sys
from pathlib import Path
# Add parent to path
sys.path.insert(0, str(Path(__file__).parent))
sys.path.insert(0, str(Path(__file__).parent.parent / "external_factors"))
from realtime_exf_service import RealTimeExFService, Parsers, INDICATORS
# === CONTROL VALUES FROM KNOWN HISTORICAL DATA ===
# March 19, 2026 12:00 UTC (verified from API)
CONTROL_VALUES_MAR_2026 = {
'timestamp': 1773931200,
'date': '2026-03-19T12:00:00+00:00',
'dvol_btc': 54.88,
'dvol_eth': 79.13,
'fund_dbt_btc': 2.7150135764992048e-06,
'fund_dbt_eth': -8.348741006833234e-07,
}
# February 15, 2026 12:00 UTC (verified from API)
CONTROL_VALUES_FEB_2026 = {
'timestamp': 1739611200,
'date': '2026-02-15T12:00:00+00:00',
'fund_dbt_btc': 0.0001770952404538274, # 17.7 bps - elevated funding
'fund_dbt_eth': -4.402918249409541e-06, # -0.44 bps - negative funding
}
# Valid ranges for sanity checks
VALID_RANGES = {
'dvol_btc': (30.0, 150.0), # BTC vol typically 30-150%
'dvol_eth': (40.0, 200.0), # ETH vol typically 40-200%
'fund_dbt_btc': (-0.001, 0.001), # -100 bps to +100 bps
'fund_dbt_eth': (-0.001, 0.001), # -100 bps to +100 bps
}
# Mock API responses
MOCK_DVOL_RESPONSE_MAR_2026 = {
"jsonrpc": "2.0",
"result": {
"data": [
[1773931140000, 54.87, 54.87, 54.86, 54.86], # 11:59
[1773931200000, 54.87, 54.88, 54.87, CONTROL_VALUES_MAR_2026['dvol_btc']], # 12:00
[1773931260000, 54.88, 54.88, 54.88, CONTROL_VALUES_MAR_2026['dvol_btc']], # 12:01
],
"status": "ok"
},
"usIn": 1773931200000000,
"usOut": 1773931200000100,
"usDiff": 100
}
MOCK_FUNDING_RESPONSE_MAR_2026 = {
"jsonrpc": "2.0",
"result": CONTROL_VALUES_MAR_2026['fund_dbt_btc'],
"usIn": 1773931200000000,
"usOut": 1773931200000100,
"usDiff": 100
}
MOCK_FUNDING_RESPONSE_FEB_2026 = {
"jsonrpc": "2.0",
"result": CONTROL_VALUES_FEB_2026['fund_dbt_btc'],
"usIn": 1739611200000000,
"usOut": 1739611200000100,
"usDiff": 100
}
class TestDeribitURLBuilder(unittest.TestCase):
"""Test the _build_deribit_url method."""
def setUp(self):
self.svc = RealTimeExFService()
def test_dvol_btc_url_structure(self):
"""DVOL BTC URL should contain required parameters."""
url = self.svc._build_deribit_url('dvol:BTC')
self.assertIn('currency=BTC', url)
self.assertIn('resolution=60', url)
self.assertIn('start_timestamp=', url)
self.assertIn('end_timestamp=', url)
self.assertIn('get_volatility_index_data', url)
def test_dvol_eth_url_structure(self):
"""DVOL ETH URL should contain ETH parameter."""
url = self.svc._build_deribit_url('dvol:ETH')
self.assertIn('currency=ETH', url)
self.assertIn('get_volatility_index_data', url)
def test_funding_btc_url_structure(self):
"""Funding BTC URL should contain instrument name."""
url = self.svc._build_deribit_url('funding:BTC-PERPETUAL')
self.assertIn('instrument_name=BTC-PERPETUAL', url)
self.assertIn('get_funding_rate_value', url)
self.assertIn('start_timestamp=', url)
self.assertIn('end_timestamp=', url)
def test_timestamp_range(self):
"""Timestamps should bracket a reasonable time window."""
url = self.svc._build_deribit_url('dvol:BTC')
# Extract timestamps
import re
start_match = re.search(r'start_timestamp=(\d+)', url)
end_match = re.search(r'end_timestamp=(\d+)', url)
self.assertIsNotNone(start_match)
self.assertIsNotNone(end_match)
start_ts = int(start_match.group(1))
end_ts = int(end_match.group(1))
# End should be after start
self.assertGreater(end_ts, start_ts)
# Window should be approximately 2 hours for DVOL
window_ms = end_ts - start_ts
self.assertAlmostEqual(window_ms, 7200 * 1000, delta=60000) # ±1 minute
def test_funding_window_24h(self):
"""Funding URL should use 24-hour window."""
url = self.svc._build_deribit_url('funding:BTC-PERPETUAL')
import re
start_match = re.search(r'start_timestamp=(\d+)', url)
end_match = re.search(r'end_timestamp=(\d+)', url)
start_ts = int(start_match.group(1))
end_ts = int(end_match.group(1))
# Window should be approximately 24 hours
window_ms = end_ts - start_ts
self.assertAlmostEqual(window_ms, 86400 * 1000, delta=60000)
class TestDeribitParsersMarch2026(unittest.TestCase):
"""Test parsers against March 2026 control values."""
def test_parse_dvol_btc_march_control(self):
"""Parser should extract March 2026 control value 54.88."""
result = Parsers.parse_deribit_dvol(MOCK_DVOL_RESPONSE_MAR_2026)
self.assertIsInstance(result, float)
self.assertAlmostEqual(
result,
CONTROL_VALUES_MAR_2026['dvol_btc'],
places=2,
msg=f"Expected {CONTROL_VALUES_MAR_2026['dvol_btc']}, got {result}"
)
def test_parse_dvol_eth_march_control(self):
"""Parser should extract March 2026 ETH control value 79.13."""
# Modify mock for ETH
mock_eth = json.loads(json.dumps(MOCK_DVOL_RESPONSE_MAR_2026))
mock_eth['result']['data'] = [
[1773931200000, 79.10, 79.15, 79.10, CONTROL_VALUES_MAR_2026['dvol_eth']]
]
result = Parsers.parse_deribit_dvol(mock_eth)
self.assertIsInstance(result, float)
self.assertAlmostEqual(
result,
CONTROL_VALUES_MAR_2026['dvol_eth'],
places=1
)
def test_parse_funding_btc_march_control(self):
"""Parser should extract March 2026 funding control value."""
result = Parsers.parse_deribit_fund(MOCK_FUNDING_RESPONSE_MAR_2026)
self.assertIsInstance(result, float)
self.assertAlmostEqual(
result,
CONTROL_VALUES_MAR_2026['fund_dbt_btc'],
places=15
)
def test_parse_funding_btc_feb_control(self):
"""Parser should extract February 2026 funding control value (17.7 bps)."""
result = Parsers.parse_deribit_fund(MOCK_FUNDING_RESPONSE_FEB_2026)
self.assertIsInstance(result, float)
self.assertAlmostEqual(
result,
CONTROL_VALUES_FEB_2026['fund_dbt_btc'],
places=15
)
# Verify it's in the elevated range (positive funding = shorts pay longs)
self.assertGreater(result, 0.0001) # > 10 bps
class TestDeribitParsersEdgeCases(unittest.TestCase):
"""Test parser edge cases and error handling."""
def test_parse_dvol_empty_response(self):
"""Parser should return 0.0 for empty response."""
result = Parsers.parse_deribit_dvol({})
self.assertEqual(result, 0.0)
def test_parse_dvol_no_data(self):
"""Parser should return 0.0 when no data array."""
result = Parsers.parse_deribit_dvol({"result": {"data": []}})
self.assertEqual(result, 0.0)
def test_parse_dvol_missing_result(self):
"""Parser should return 0.0 when result missing."""
result = Parsers.parse_deribit_dvol({"error": "some_error"})
self.assertEqual(result, 0.0)
def test_parse_funding_scalar_result(self):
"""Parser should handle scalar (non-array) result."""
mock = {"result": 0.00001}
result = Parsers.parse_deribit_fund(mock)
self.assertEqual(result, 0.00001)
def test_parse_funding_list_result(self):
"""Parser should handle list result with interest_8h field."""
mock = {"result": [{"interest_8h": 0.00002}]}
result = Parsers.parse_deribit_fund(mock)
self.assertEqual(result, 0.00002)
def test_parse_funding_empty(self):
"""Parser should return 0.0 for empty funding response."""
result = Parsers.parse_deribit_fund({})
self.assertEqual(result, 0.0)
class TestIndicatorConfig(unittest.TestCase):
"""Test that indicator metadata is correctly configured."""
def test_dvol_btc_config(self):
"""DVOL BTC should have correct metadata."""
meta = INDICATORS['dvol_btc']
self.assertEqual(meta.name, 'dvol_btc')
self.assertEqual(meta.source, 'deribit')
self.assertTrue(meta.url.startswith('dvol:'))
self.assertEqual(meta.parser, 'parse_deribit_dvol')
self.assertTrue(meta.acb_critical)
self.assertEqual(meta.optimal_lag_days, 1)
def test_dvol_eth_config(self):
"""DVOL ETH should have correct metadata."""
meta = INDICATORS['dvol_eth']
self.assertEqual(meta.name, 'dvol_eth')
self.assertTrue(meta.url.startswith('dvol:'))
self.assertTrue(meta.acb_critical)
def test_funding_btc_config(self):
"""Funding BTC should have correct metadata."""
meta = INDICATORS['fund_dbt_btc']
self.assertEqual(meta.name, 'fund_dbt_btc')
self.assertTrue(meta.url.startswith('funding:'))
def test_all_deribit_indicators_exist(self):
"""All expected Deribit indicators should be configured."""
expected = ['dvol_btc', 'dvol_eth', 'fund_dbt_btc', 'fund_dbt_eth']
for ind in expected:
self.assertIn(ind, INDICATORS)
self.assertEqual(INDICATORS[ind].source, 'deribit')
class TestLiveAPIRanges(unittest.TestCase):
"""Live API tests - validates current values are in expected ranges."""
def test_live_dvol_in_valid_range(self):
"""Live DVOL should be in valid range (30-150 for BTC)."""
import os
if os.environ.get('RUN_LIVE_TESTS') != '1':
self.skipTest("Set RUN_LIVE_TESTS=1 to run live tests")
import aiohttp
import asyncio
async def fetch():
svc = RealTimeExFService()
url = svc._build_deribit_url('dvol:BTC')
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
self.assertEqual(resp.status, 200)
data = await resp.json()
value = Parsers.parse_deribit_dvol(data)
# Validate range
min_val, max_val = VALID_RANGES['dvol_btc']
self.assertGreaterEqual(value, min_val)
self.assertLessEqual(value, max_val)
return value
value = asyncio.run(fetch())
print(f"\nLive DVOL BTC: {value} (valid range: {VALID_RANGES['dvol_btc']})")
def test_live_funding_in_valid_range(self):
"""Live funding should be in valid range (-0.001 to +0.001)."""
import os
if os.environ.get('RUN_LIVE_TESTS') != '1':
self.skipTest("Set RUN_LIVE_TESTS=1 to run live tests")
import aiohttp
import asyncio
async def fetch():
svc = RealTimeExFService()
url = svc._build_deribit_url('funding:BTC-PERPETUAL')
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
data = await resp.json()
value = Parsers.parse_deribit_fund(data)
min_val, max_val = VALID_RANGES['fund_dbt_btc']
self.assertGreaterEqual(value, min_val)
self.assertLessEqual(value, max_val)
return value
value = asyncio.run(fetch())
print(f"\nLive Funding BTC: {value}")
def run_tests():
"""Run all tests."""
loader = unittest.TestLoader()
suite = unittest.TestSuite()
# Add all test classes
suite.addTests(loader.loadTestsFromTestCase(TestDeribitURLBuilder))
suite.addTests(loader.loadTestsFromTestCase(TestDeribitParsersMarch2026))
suite.addTests(loader.loadTestsFromTestCase(TestDeribitParsersEdgeCases))
suite.addTests(loader.loadTestsFromTestCase(TestIndicatorConfig))
# Skip live tests by default
# suite.addTests(loader.loadTestsFromTestCase(TestLiveAPIRanges))
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result.wasSuccessful()
if __name__ == '__main__':
success = run_tests()
sys.exit(0 if success else 1)