""" Tests for funding arb strategy and WQI predictor integration. """ import math class TestFundingArb: def test_no_entry_below_threshold(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30) trade = arb.tick(0.10, 100000.0, 0.0) assert trade is None assert arb.position == 0 def test_entry_above_threshold_positive(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30) trade = arb.tick(0.50, 100000.0, 0.0) assert trade is not None assert trade["action"] == "SELL" assert arb.position == -1 def test_entry_above_threshold_negative(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30) trade = arb.tick(-0.50, 100000.0, 0.0) assert trade is not None assert trade["action"] == "BUY" assert arb.position == 1 def test_exit_when_apr_fades(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30, apr_exit=0.10) arb.tick(0.50, 100000.0, 0.0) trade = arb.tick(0.05, 100000.0, 3600.0) assert trade is not None assert "EXIT" in trade["action"] assert arb.position == 0 def test_exit_when_funding_flips(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30, apr_exit=0.10) arb.tick(0.50, 100000.0, 0.0) trade = arb.tick(-0.10, 100000.0, 3600.0) assert trade is not None assert "EXIT" in trade["action"] assert arb.position == 0 def test_signal_no_exit_when_apr_still_high(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30) arb.tick(0.50, 100000.0, 0.0) result = arb.signal(0.60, 100000.0) assert result["action"] == "HOLD" def test_signal_hold_when_below_threshold(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30) result = arb.signal(0.05) assert result["action"] == "HOLD" def test_summary_no_trades(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb() s = arb.summary() assert s["total_trades"] == 0 assert s["win_rate"] == 0.0 def test_summary_with_trades(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30, apr_exit=0.10) arb.tick(0.50, 100000.0, 0.0) arb.tick(0.05, 100100.0, 3600.0) arb.tick(0.50, 100000.0, 7200.0) arb.tick(0.05, 100050.0, 10800.0) s = arb.summary() assert s["total_trades"] == 2 assert s["position"] == 0 def test_reset(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.30, apr_exit=0.10) arb.tick(0.50, 100000.0, 0.0) arb.reset() assert arb.position == 0 assert len(arb.trades) == 0 def test_backtest_empty(self): from strategies.funding_arb_strategy import backtest_funding_arb result = backtest_funding_arb([], []) assert result["total_trades"] == 0 def test_backtest_single_trade(self): from strategies.funding_arb_strategy import backtest_funding_arb rates = [0.50, 0.06] prices = [100000.0, 100000.0] result = backtest_funding_arb(rates, prices, apr_threshold=0.30) assert result["total_trades"] == 1 def test_fee_accounting(self): from strategies.funding_arb_strategy import FundingArb arb = FundingArb(apr_threshold=0.10, size=0.001, taker_fee_pct=0.00045) trade = arb.tick(0.50, 100000.0, 0.0) assert trade is not None expected_fee = 0.001 * 100000.0 * 0.00045 assert abs(trade["fee"] - expected_fee) < 0.001 class TestFundingDiscoveryCLI: def test_discovery_no_data(self): from strategies.funding_arb_strategy import run_funding_discovery result = run_funding_discovery( data_dir="/tmp/nonexistent_data", coin="BTC", ) assert "error" in result def test_backtest_multiple_thresholds(self): from strategies.funding_arb_strategy import backtest_funding_arb import random random.seed(42) rates = [abs(random.gauss(0, 0.5)) for _ in range(200)] prices = [100000.0 + random.gauss(0, 500) for _ in range(200)] for threshold in [0.10, 0.30, 0.50]: result = backtest_funding_arb(rates, prices, apr_threshold=threshold) assert "total_trades" in result assert "total_net_pnl" in result class TestWQIIntegration: def test_wqi_with_node_interface(self): from strategies.wqi_predictor import WQIPredictor wqi = WQIPredictor(z_entry=2.0, max_hold_seconds=30) bids = [(50000.0, 1.0), (49999.0, 0.5)] asks = [(50002.0, 1.0), (50003.0, 0.5)] for _ in range(30): wqi.feed_signal(bids, asks, 50001.0) extreme_bids = [(50000.0, 10.0), (49999.0, 5.0)] extreme_asks = [(50002.0, 0.5)] signal = wqi.feed_signal(extreme_bids, extreme_asks, 50001.0) assert signal["action"] in ("BUY", "HOLD") if signal["action"] == "BUY": assert wqi.position != 0 def test_wqi_exit_on_timeout(self): from strategies.wqi_predictor import WQIPredictor import time wqi = WQIPredictor(z_entry=0.01, z_exit=999.0, wqi_threshold=0.01, max_hold_seconds=0.001, max_adverse=999.0) for _ in range(30): wqi.feed_signal([(100.0, 1.0)], [(102.0, 1.0)], 101.0) extreme_bids = [(100.0, 20.0), (99.0, 10.0)] extreme_asks = [(102.0, 1.0)] signal = wqi.feed_signal(extreme_bids, extreme_asks, 101.0) if signal["action"] in ("BUY", "SELL"): time.sleep(0.01) signal2 = wqi.feed_signal(extreme_bids, extreme_asks, 101.0) assert signal2["action"] in ("EXIT", "HOLD") class TestNodeV2Strategies: def test_node_creates_all_strategies(self): from live.node_v2 import ProductionNode node = ProductionNode( coins=["BTC"], testnet=True, mode="paper", max_position_per_coin=0.001, base_quote_size=0.0001, ) assert len(node._wqi_predictors) == 1 assert node._funding_arb is not None assert node._fill_model is not None def test_wqi_not_none(self): from live.node_v2 import ProductionNode node = ProductionNode( coins=["BTC"], testnet=True, mode="paper", max_position_per_coin=0.001, ) wqi = node._wqi_predictors.get("BTC") assert wqi is not None assert wqi._z_entry == 2.0 assert wqi._max_hold_seconds == 30 def test_funding_arb_config(self): from live.node_v2 import ProductionNode node = ProductionNode( coins=["BTC"], testnet=True, mode="paper", max_position_per_coin=0.001, ) arb = node._funding_arb assert arb._apr_threshold == 0.30 assert arb._apr_exit == 0.10