""" Tests for live/strategies/funding_whipsaw.py — premium index decay trading. """ from unittest.mock import patch, MagicMock from live.strategies.funding_whipsaw import FundingWhipsawTrader class TestFundingWhipsaw: def test_initial_no_signal_outside_window(self): """With default 60s entry window and >60s to funding, no signal.""" trader = FundingWhipsawTrader() trader.update(mark_px=64500, oracle_px=64480) with patch.object(trader, 'seconds_to_funding', return_value=300.0): s = trader.signal() assert s["action"] == "none" assert s["reason"] == "outside_entry_window" def test_enter_long_on_negative_premium(self): trader = FundingWhipsawTrader(min_premium_bps=0.5) trader.update(mark_px=64400, oracle_px=64500) # negative premium: -1.55 bps with patch.object(trader, 'seconds_to_funding', return_value=30.0): s = trader.signal() assert s["action"] == "enter_long" assert s["side"] == "buy" assert s["confidence"] > 0 assert s["size"] > 0 def test_enter_short_on_positive_premium(self): trader = FundingWhipsawTrader(min_premium_bps=0.5) trader.update(mark_px=64600, oracle_px=64500) # positive premium: +1.55 bps with patch.object(trader, 'seconds_to_funding', return_value=30.0): s = trader.signal() assert s["action"] == "enter_short" assert s["side"] == "sell" assert s["confidence"] > 0 def test_no_signal_on_small_premium(self): trader = FundingWhipsawTrader(min_premium_bps=5.0) trader.update(mark_px=64501, oracle_px=64500) # tiny premium: 0.015 bps with patch.object(trader, 'seconds_to_funding', return_value=30.0): s = trader.signal() assert s["action"] == "none" assert s["reason"] == "premium_too_small" def test_close_after_funding(self): """After entering, close when funding settles.""" trader = FundingWhipsawTrader() trader.update(mark_px=64600, oracle_px=64500) with patch.object(trader, 'seconds_to_funding', return_value=30.0): s = trader.signal() assert s["action"].startswith("enter") # Now funding just happened with patch.object(trader, 'seconds_to_funding', return_value=8*3600 - 2.0): with patch.object(trader, 'seconds_since_funding', return_value=2.0): s = trader.signal() assert s["action"] == "close" def test_hold_after_entry(self): trader = FundingWhipsawTrader() trader.update(mark_px=64600, oracle_px=64500) with patch.object(trader, 'seconds_to_funding', return_value=30.0): trader.signal() # enter # Next tick, still before funding with patch.object(trader, 'seconds_to_funding', return_value=25.0): s = trader.signal() assert s["action"] == "hold" def test_larger_position_with_more_premium(self): trader = FundingWhipsawTrader(min_premium_bps=0.5, max_size=0.001) trader.update(mark_px=65100, oracle_px=64500) # large premium with patch.object(trader, 'seconds_to_funding', return_value=30.0): s = trader.signal() assert s["confidence"] > 0.5 # high confidence assert s["size"] > 0 def test_seconds_to_funding_returns_positive(self): trader = FundingWhipsawTrader() secs = trader.seconds_to_funding() assert secs > 0 assert secs <= 8 * 3600 # Max 8 hours def test_signal_includes_premium_info(self): trader = FundingWhipsawTrader() trader.update(mark_px=64600, oracle_px=64500) with patch.object(trader, 'seconds_to_funding', return_value=45.0): s = trader.signal() assert "premium_bps" in s assert "seconds_to_funding" in s assert "confidence" in s assert s["premium_bps"] > 0