# OKX Codex Trader Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build a minimal Python CLI that fetches OKX swap candles, backtests a fixed `10/20` SMA strategy, asks local Codex CLI for a validated trading signal, and sends OKX demo orders for `BTC-USDT-SWAP` and `ETH-USDT-SWAP`. **Architecture:** The project is a small package rooted at `okx_codex_trader/`. `cli.py` owns argument parsing and command dispatch, `okx_client.py` owns OKX HTTP signing and REST calls, `backtest.py` owns deterministic SMA backtesting, and `codex_analyzer.py` owns subprocess execution of `codex` plus signal validation. Shared dataclasses live in `models.py`, and `config.py` enforces the runtime contract at the boundary. **Tech Stack:** Python 3.11+, `pytest`, `requests`, `argparse`, `dataclasses`, `subprocess`, standard library JSON and HMAC utilities --- ## File Structure - Create: `/home/lxy/okx-codex-trader/pyproject.toml` - project metadata and dependencies - Create: `/home/lxy/okx-codex-trader/README.md` - setup, environment variables, and command examples - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/__init__.py` - package marker - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/config.py` - required environment variables and runtime checks - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/models.py` - normalized dataclasses for candles, signals, orders, positions, and backtest results - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/strategy.py` - signal validation helpers and SMA crossover helpers - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/backtest.py` - deterministic `10/20` SMA backtest engine with `10_000 USDT` initial equity - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/okx_client.py` - signed demo REST client, instrument lookup, candle fetch, position mode check, leverage set, order placement, positions fetch - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/codex_analyzer.py` - local `codex` subprocess call and JSON validation - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py` - command entrypoint and JSON stdout formatting - Create: `/home/lxy/okx-codex-trader/tests/test_config.py` - config boundary tests - Create: `/home/lxy/okx-codex-trader/tests/test_strategy.py` - signal validation tests - Create: `/home/lxy/okx-codex-trader/tests/test_backtest.py` - fixed-series backtest tests - Create: `/home/lxy/okx-codex-trader/tests/test_okx_client.py` - signed request, demo header, sizing, and hedge-mode tests - Create: `/home/lxy/okx-codex-trader/tests/test_codex_analyzer.py` - subprocess and JSON parsing tests - Create: `/home/lxy/okx-codex-trader/tests/test_cli.py` - command parsing and dispatch tests ### Task 1: Project Skeleton **Files:** - Create: `/home/lxy/okx-codex-trader/pyproject.toml` - Create: `/home/lxy/okx-codex-trader/README.md` - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/__init__.py` - [ ] **Step 1: Write the failing packaging smoke test** ```python import okx_codex_trader def test_package_exports_version(): assert okx_codex_trader.__version__ == "0.1.0" ``` - [ ] **Step 2: Run test to verify it fails** Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py -k package_exports_version -v` Expected: FAIL because the package module does not exist yet - [ ] **Step 3: Write minimal package metadata** ```toml [project] name = "okx-codex-trader" version = "0.1.0" dependencies = ["requests>=2.32,<3"] [project.scripts] okx-codex-trader = "okx_codex_trader.cli:main" ``` ```python __version__ = "0.1.0" ``` ```bash git -C /home/lxy/okx-codex-trader init ``` - [ ] **Step 4: Run test to verify it passes** Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py -k package_exports_version -v` Expected: PASS - [ ] **Step 5: Commit** ```bash git -C /home/lxy/okx-codex-trader add pyproject.toml README.md okx_codex_trader/__init__.py tests/test_config.py git -C /home/lxy/okx-codex-trader commit -m "chore: scaffold okx codex trader project" ``` ### Task 2: Config And Models **Files:** - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/config.py` - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/models.py` - Test: `/home/lxy/okx-codex-trader/tests/test_config.py` - Test: `/home/lxy/okx-codex-trader/tests/test_strategy.py` - [ ] **Step 1: Write the failing config and signal validation tests** ```python def test_load_config_requires_okx_credentials(monkeypatch): monkeypatch.delenv("OKX_API_KEY", raising=False) monkeypatch.delenv("OKX_API_SECRET", raising=False) monkeypatch.delenv("OKX_API_PASSPHRASE", raising=False) with pytest.raises(ValueError): load_config() def test_validate_signal_rejects_leverage_out_of_range(): with pytest.raises(ValueError): validate_signal({"action": "long", "confidence": 0.9, "leverage": 4, "entry_price": None, "take_profit_price": None, "stop_loss_price": None, "reason": "x"}) def test_validate_signal_rejects_unknown_action(): with pytest.raises(ValueError): validate_signal({"action": "hold", "confidence": 0.9, "leverage": 2, "entry_price": None, "take_profit_price": None, "stop_loss_price": None, "reason": "x"}) def test_validate_signal_rejects_confidence_out_of_range(): with pytest.raises(ValueError): validate_signal({"action": "long", "confidence": 1.2, "leverage": 2, "entry_price": None, "take_profit_price": None, "stop_loss_price": None, "reason": "x"}) def test_validate_signal_requires_full_shape(): with pytest.raises(ValueError): validate_signal({"action": "long", "confidence": 0.9, "leverage": 2}) ``` - [ ] **Step 2: Run tests to verify they fail** Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py /home/lxy/okx-codex-trader/tests/test_strategy.py -v` Expected: FAIL because `load_config` and `validate_signal` are not implemented - [ ] **Step 3: Write minimal config and model code** ```python @dataclass(frozen=True) class Candle: symbol: str ts: int open: float high: float low: float close: float volume: float @dataclass(frozen=True) class TradeSignal: action: Literal["long", "short", "flat"] confidence: float leverage: int entry_price: float | None take_profit_price: float | None stop_loss_price: float | None reason: str @dataclass(frozen=True) class InstrumentMeta: ct_val: float lot_sz: float min_sz: float @dataclass(frozen=True) class Position: symbol: str pos_side: str size: float avg_price: float @dataclass(frozen=True) class OrderResult: status: str order_id: str | None symbol: str side: str | None pos_side: str | None order_type: str | None size: float | None @dataclass(frozen=True) class BacktestTrade: direction: str entry_price: float exit_price: float margin_used: float ending_equity: float @dataclass(frozen=True) class BacktestResult: initial_equity: float ending_equity: float total_return: float max_drawdown: float win_rate: float trade_count: int trades: list[BacktestTrade] def to_dict(self) -> dict[str, object]: ... @dataclass(frozen=True) class Config: api_key: str api_secret: str api_passphrase: str def load_config(env: Mapping[str, str] | None = None) -> Config: source = os.environ if env is None else env ... ``` - [ ] **Step 4: Run tests to verify they pass** Run: `pytest /home/lxy/okx-codex-trader/tests/test_config.py /home/lxy/okx-codex-trader/tests/test_strategy.py -v` Expected: PASS - [ ] **Step 5: Commit** ```bash git -C /home/lxy/okx-codex-trader add okx_codex_trader/config.py okx_codex_trader/models.py okx_codex_trader/strategy.py tests/test_config.py tests/test_strategy.py git -C /home/lxy/okx-codex-trader commit -m "feat: add config and signal validation models" ``` ### Task 3: Backtest Engine **Files:** - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/backtest.py` - Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/strategy.py` - Test: `/home/lxy/okx-codex-trader/tests/test_backtest.py` - [ ] **Step 1: Write the failing SMA backtest test** ```python def test_backtest_runs_fixed_sma_crossover_series(): candles = build_crossing_series() result = run_backtest(candles=candles, leverage=2) assert result.initial_equity == 10_000 assert result.trade_count == 2 assert result.trades[0].entry_price == candles[21].open assert result.trades[0].exit_price == candles[30].open assert result.trades[0].margin_used == 10_000 assert result.trades[1].margin_used == result.trades[0].ending_equity assert result.ending_equity == 4_888.888888888889 assert result.total_return == -0.5111111111111112 assert "total_return" in result.to_dict() assert "max_drawdown" in result.to_dict() assert result.win_rate == 0.5 ``` - [ ] **Step 2: Run test to verify it fails** Run: `pytest /home/lxy/okx-codex-trader/tests/test_backtest.py -v` Expected: FAIL because `run_backtest` does not exist - [ ] **Step 3: Write minimal backtest implementation** ```python def run_backtest(candles: list[Candle], leverage: int) -> BacktestResult: fast = simple_moving_average(candles, 10) slow = simple_moving_average(candles, 20) ... ``` - [ ] **Step 4: Run test to verify it passes** Run: `pytest /home/lxy/okx-codex-trader/tests/test_backtest.py -v` Expected: PASS - [ ] **Step 5: Commit** ```bash git -C /home/lxy/okx-codex-trader add okx_codex_trader/backtest.py okx_codex_trader/strategy.py tests/test_backtest.py git -C /home/lxy/okx-codex-trader commit -m "feat: add deterministic sma backtest engine" ``` ### Task 4: OKX Demo Client **Files:** - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/okx_client.py` - Test: `/home/lxy/okx-codex-trader/tests/test_okx_client.py` - [ ] **Step 1: Write the failing client tests** ```python def test_signed_demo_request_attaches_headers(): session = DummySession() client = OkxClient(config=sample_config(), session=session) client.get_candles(symbol="BTC-USDT-SWAP", bar="1H", limit=20) request = session.last_request assert request.headers["x-simulated-trading"] == "1" assert request.headers["OK-ACCESS-KEY"] == "key" def test_build_contract_size_rounds_down_to_lot_size(): metadata = InstrumentMeta(ct_val=0.01, lot_sz=0.1, min_sz=0.1) assert build_contract_size(notional=251, price=25_000, metadata=metadata) == 1.0 def test_build_contract_size_fails_below_min_size(): metadata = InstrumentMeta(ct_val=0.01, lot_sz=1, min_sz=5) with pytest.raises(ValueError): build_contract_size(notional=250, price=25_100, metadata=metadata) def test_market_order_fetches_latest_price_before_sizing(): session = DummySession([ instrument_response(), ticker_response(last="25000"), account_config_response(pos_mode="long_short_mode"), leverage_response(), place_order_response(), ]) client = OkxClient(config=sample_config(), session=session) client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100) assert session.request_paths == [ "/api/v5/public/instruments", "/api/v5/market/ticker", "/api/v5/account/config", "/api/v5/account/set-leverage", "/api/v5/trade/order", ] def test_place_demo_order_fails_when_not_hedge_mode(): session = DummySession([ instrument_response(), ticker_response(last="25000"), account_config_response(pos_mode="net_mode"), ]) client = OkxClient(config=sample_config(), session=session) with pytest.raises(ValueError): client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100) def test_limit_short_order_uses_sell_and_short_pos_side(): session = DummySession([ instrument_response(), account_config_response(pos_mode="long_short_mode"), leverage_response(), place_order_response(), ]) client = OkxClient(config=sample_config(), session=session) client.place_demo_order(symbol="ETH-USDT-SWAP", signal=limit_short_signal(), margin_usdt=100) order_request = session.last_json_body assert order_request["ordType"] == "limit" assert order_request["side"] == "sell" assert order_request["posSide"] == "short" assert order_request["px"] == "25000" assert session.request_bodies[2]["lever"] == "2" assert session.request_bodies[2]["mgnMode"] == "isolated" def test_flat_signal_returns_noop_without_order_submission(): session = DummySession([]) client = OkxClient(config=sample_config(), session=session) result = client.place_demo_order(symbol="BTC-USDT-SWAP", signal=flat_signal(), margin_usdt=100) assert result.status == "noop" assert session.request_paths == [] def test_place_demo_order_sends_computed_sz_and_ignores_tp_sl_fields(): session = DummySession([ instrument_response(), ticker_response(last="25000"), account_config_response(pos_mode="long_short_mode"), leverage_response(), place_order_response(), ]) client = OkxClient(config=sample_config(), session=session) client.place_demo_order(symbol="BTC-USDT-SWAP", signal=market_long_signal(), margin_usdt=100) order_request = session.last_json_body assert order_request["sz"] == "8" assert "tpTriggerPx" not in order_request assert "slTriggerPx" not in order_request def test_okx_error_payload_raises_value_error(): session = DummySession([error_response(code="51000", msg="parameter error")]) client = OkxClient(config=sample_config(), session=session) with pytest.raises(ValueError): client.get_candles(symbol="BTC-USDT-SWAP", bar="1H", limit=20) def test_get_positions_returns_normalized_positions(): session = DummySession([positions_response()]) client = OkxClient(config=sample_config(), session=session) positions = client.get_positions(symbol="BTC-USDT-SWAP") assert positions[0].symbol == "BTC-USDT-SWAP" ``` - [ ] **Step 2: Run tests to verify they fail** Run: `pytest /home/lxy/okx-codex-trader/tests/test_okx_client.py -v` Expected: FAIL because `OkxClient` and sizing helpers do not exist - [ ] **Step 3: Write minimal client implementation** ```python class OkxClient: base_url = "https://www.okx.com" def get_candles(self, symbol: str, bar: str, limit: int) -> list[Candle]: ... def get_instrument_meta(self, symbol: str) -> InstrumentMeta: ... def get_last_price(self, symbol: str) -> float: ... def ensure_hedge_mode(self) -> None: ... def set_leverage(self, symbol: str, leverage: int, pos_side: str) -> None: ... def place_demo_order(self, symbol: str, signal: TradeSignal, margin_usdt: float) -> OrderResult: ... def get_positions(self, symbol: str) -> list[Position]: ... ``` - [ ] **Step 4: Run tests to verify they pass** Run: `pytest /home/lxy/okx-codex-trader/tests/test_okx_client.py -v` Expected: PASS - [ ] **Step 5: Commit** ```bash git -C /home/lxy/okx-codex-trader add okx_codex_trader/okx_client.py tests/test_okx_client.py git -C /home/lxy/okx-codex-trader commit -m "feat: add okx demo client and contract sizing" ``` ### Task 5: Codex Analyzer **Files:** - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/codex_analyzer.py` - Modify: `/home/lxy/okx-codex-trader/okx_codex_trader/strategy.py` - Test: `/home/lxy/okx-codex-trader/tests/test_codex_analyzer.py` - [ ] **Step 1: Write the failing analyzer tests** ```python 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) ``` - [ ] **Step 2: Run tests to verify they fail** Run: `pytest /home/lxy/okx-codex-trader/tests/test_codex_analyzer.py -v` Expected: FAIL because `analyze_with_codex` does not exist - [ ] **Step 3: Write minimal analyzer implementation** ```python def analyze_with_codex(...): command = ["codex", "exec", prompt] completed = runner(command, capture_output=True, text=True, check=False) ... ``` - [ ] **Step 4: Run tests to verify they pass** Run: `pytest /home/lxy/okx-codex-trader/tests/test_codex_analyzer.py -v` Expected: PASS - [ ] **Step 5: Commit** ```bash git -C /home/lxy/okx-codex-trader add okx_codex_trader/codex_analyzer.py okx_codex_trader/strategy.py tests/test_codex_analyzer.py git -C /home/lxy/okx-codex-trader commit -m "feat: add codex analyzer integration" ``` ### Task 6: CLI Commands **Files:** - Create: `/home/lxy/okx-codex-trader/okx_codex_trader/cli.py` - Modify: `/home/lxy/okx-codex-trader/README.md` - Test: `/home/lxy/okx-codex-trader/tests/test_cli.py` - [ ] **Step 1: Write the failing CLI tests** ```python def build_main_with_stubs(): client = fake_client() main = main_factory( load_config=lambda: sample_config(), client_factory=lambda config: client, analyze_fn=fake_analyze_with_codex, write_text=real_write_text, ) return main, client def test_fetch_history_prints_candle_json(capsys): main, client = build_main_with_stubs() exit_code = main(["fetch-history", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "20"]) assert exit_code == 0 assert client.get_candles_called_with == ("BTC-USDT-SWAP", "1H", 20) assert '"symbol": "BTC-USDT-SWAP"' in capsys.readouterr().out def test_backtest_prints_summary_json(capsys): main, client = build_main_with_stubs() exit_code = main(["backtest", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "50", "--leverage", "2"]) assert exit_code == 0 assert client.get_candles_called_with == ("BTC-USDT-SWAP", "1H", 50) assert '"trade_count"' in capsys.readouterr().out def test_analyze_writes_output_file_and_stdout(tmp_path, capsys): main, client = build_main_with_stubs() output_file = tmp_path / "signal.json" exit_code = main([ "analyze", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "20", "--output-file", str(output_file), ]) assert exit_code == 0 assert output_file.exists() assert client.get_candles_called_with == ("BTC-USDT-SWAP", "1H", 20) assert '"action"' in capsys.readouterr().out def test_paper_order_reads_signal_file_and_outputs_order_json(tmp_path, capsys): main, client = build_main_with_stubs() signal_file = tmp_path / "signal.json" signal_file.write_text(json.dumps(valid_signal())) exit_code = main([ "paper-order", "--symbol", "BTC-USDT-SWAP", "--signal-file", str(signal_file), "--margin-usdt", "100", ]) assert exit_code == 0 assert client.place_demo_order_called assert '"status"' in capsys.readouterr().out def test_positions_prints_position_json(capsys): main, client = build_main_with_stubs() exit_code = main(["positions", "--symbol", "BTC-USDT-SWAP"]) assert exit_code == 0 assert client.get_positions_called_with == "BTC-USDT-SWAP" assert '"symbol": "BTC-USDT-SWAP"' in capsys.readouterr().out def test_cli_rejects_unsupported_symbol(): main, _ = build_main_with_stubs() with pytest.raises(SystemExit): main(["fetch-history", "--symbol", "SOL-USDT-SWAP", "--bar", "1H", "--limit", "20"]) def test_cli_rejects_leverage_out_of_range(): main, _ = build_main_with_stubs() with pytest.raises(SystemExit): main(["backtest", "--symbol", "BTC-USDT-SWAP", "--bar", "1H", "--limit", "50", "--leverage", "4"]) ``` - [ ] **Step 2: Run tests to verify they fail** Run: `pytest /home/lxy/okx-codex-trader/tests/test_cli.py -v` Expected: FAIL because `main` and command dispatch do not exist - [ ] **Step 3: Write minimal CLI implementation** ```python def main(argv: Sequence[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) ... ``` - [ ] **Step 4: Run tests to verify they pass** Run: `pytest /home/lxy/okx-codex-trader/tests/test_cli.py -v` Expected: PASS - [ ] **Step 5: Run the full test suite and commit** Run: `pytest /home/lxy/okx-codex-trader/tests -v` Expected: PASS ```bash git -C /home/lxy/okx-codex-trader add okx_codex_trader/cli.py README.md tests/test_cli.py git -C /home/lxy/okx-codex-trader commit -m "feat: add cli commands for okx codex trader" ``` ### Task 7: End-To-End Local Verification **Files:** - Modify: `/home/lxy/okx-codex-trader/README.md` - [ ] **Step 1: Document the exact runtime commands** ```md python -m okx_codex_trader.cli fetch-history --symbol BTC-USDT-SWAP --bar 1H --limit 50 python -m okx_codex_trader.cli backtest --symbol BTC-USDT-SWAP --bar 1H --limit 200 --leverage 2 python -m okx_codex_trader.cli analyze --symbol BTC-USDT-SWAP --bar 1H --limit 50 --output-file signal.json python -m okx_codex_trader.cli paper-order --symbol BTC-USDT-SWAP --signal-file signal.json --margin-usdt 100 python -m okx_codex_trader.cli positions --symbol BTC-USDT-SWAP ``` - [ ] **Step 2: Run the test suite one more time** Run: `pytest /home/lxy/okx-codex-trader/tests -v` Expected: PASS - [ ] **Step 3: Run one dry local command that does not require OKX credentials** Run: `python -m okx_codex_trader.cli --help` Expected: usage text printed successfully - [ ] **Step 4: Record any unverified external dependencies** Expected note: - OKX demo credentials were not exercised in automated tests - local `codex` runtime behavior outside mocked subprocess tests still requires manual verification - [ ] **Step 5: Commit** ```bash git -C /home/lxy/okx-codex-trader add README.md git -C /home/lxy/okx-codex-trader commit -m "docs: add local verification commands" ```