|
|
@@ -0,0 +1,70 @@
|
|
|
+import subprocess
|
|
|
+
|
|
|
+import pytest
|
|
|
+
|
|
|
+from okx_codex_trader.codex_analyzer import analyze_with_codex
|
|
|
+from okx_codex_trader.models import Candle
|
|
|
+
|
|
|
+
|
|
|
+def sample_candles() -> list[Candle]:
|
|
|
+ return [
|
|
|
+ Candle(symbol="BTC-USDT-SWAP", ts=1, open=100.0, high=105.0, low=99.0, close=104.0, volume=10.0),
|
|
|
+ Candle(symbol="BTC-USDT-SWAP", ts=2, open=104.0, high=106.0, low=103.0, close=105.0, volume=12.0),
|
|
|
+ Candle(symbol="BTC-USDT-SWAP", ts=3, open=105.0, high=107.0, low=104.0, close=106.0, volume=11.0),
|
|
|
+ ]
|
|
|
+
|
|
|
+
|
|
|
+def fake_runner(stdout: str):
|
|
|
+ calls: list[tuple[object, bool, bool, bool]] = []
|
|
|
+
|
|
|
+ def runner(command, capture_output: bool, text: bool, check: bool):
|
|
|
+ calls.append((command, capture_output, text, check))
|
|
|
+ return subprocess.CompletedProcess(command, 0, stdout=stdout, stderr="")
|
|
|
+
|
|
|
+ runner.calls = calls
|
|
|
+ return runner
|
|
|
+
|
|
|
+
|
|
|
+def missing_which(name: str) -> None:
|
|
|
+ assert name == "codex"
|
|
|
+ return None
|
|
|
+
|
|
|
+
|
|
|
+def test_analyzer_fails_when_codex_is_missing():
|
|
|
+ with pytest.raises(FileNotFoundError):
|
|
|
+ analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", which=missing_which)
|
|
|
+
|
|
|
+
|
|
|
+def test_analyzer_rejects_non_json_output():
|
|
|
+ runner = fake_runner(stdout="not json")
|
|
|
+
|
|
|
+ with pytest.raises(ValueError):
|
|
|
+ analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", runner=runner)
|
|
|
+
|
|
|
+
|
|
|
+def test_analyzer_rejects_json_leverage_out_of_range():
|
|
|
+ runner = fake_runner(
|
|
|
+ stdout='{"action":"long","confidence":0.8,"leverage":4,"entry_price":null,"take_profit_price":null,"stop_loss_price":null,"reason":"x"}'
|
|
|
+ )
|
|
|
+
|
|
|
+ with pytest.raises(ValueError):
|
|
|
+ analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", runner=runner)
|
|
|
+
|
|
|
+
|
|
|
+def test_analyzer_returns_valid_trade_signal():
|
|
|
+ runner = fake_runner(
|
|
|
+ stdout='{"action":"short","confidence":0.6,"leverage":2,"entry_price":101.5,"take_profit_price":99.0,"stop_loss_price":103.0,"reason":"trend"}'
|
|
|
+ )
|
|
|
+
|
|
|
+ signal = analyze_with_codex(candles=sample_candles(), symbol="BTC-USDT-SWAP", bar="1H", runner=runner)
|
|
|
+
|
|
|
+ assert signal.action == "short"
|
|
|
+ assert signal.confidence == 0.6
|
|
|
+ assert signal.leverage == 2
|
|
|
+ assert signal.entry_price == 101.5
|
|
|
+ assert signal.take_profit_price == 99.0
|
|
|
+ assert signal.stop_loss_price == 103.0
|
|
|
+ assert signal.reason == "trend"
|
|
|
+ assert runner.calls
|
|
|
+ assert runner.calls[0][0][0:2] == ["codex", "exec"]
|
|
|
+ assert runner.calls[0][1:] == (True, True, False)
|