This is an execution design only. It does not implement trading and does not submit orders.
Read scope:
okx_codex_trader/okx_client.py: current OKX client signs requests, fetches confirmed history candles, reads balances and positions, and has only a single-order place_order path.okx_codex_trader/cli.py: current trading commands are paper-order, positions, okx-account, and okx-order; the existing live submitter is intentionally not used by this plan.okx_codex_trader/paper_engine.py: current paper state assumes immediate local fills and is not an exchange-order lifecycle model.freqtrade/README.md and freqtrade/user_data/strategies/BtcRsi2Guarded.py: freqtrade is isolated as a comparison framework; it uses process_only_new_candles = True but currently contains only BTC RSI2 guarded logic, not ETH price TWAP execution.scripts/search_eth_twap_robustness_10y.py, scripts/search_eth_price_twap_variants.py, and scripts/explore_ultrashort.py: these define the ETH price-TWAP mechanics used for this design.reports/eth-exploration/eth-twap-robustness-10y-summary.md: selected candidate is rsi2-long-guarded-price-twap-o0.0030-0.0060-0.0090-v4-t60-l3.0-x50.0-sl0.012-mh48.Selected live design parameters:
| Field | Value |
|---|---|
| Symbol | ETH-USDT-SWAP |
| Timeframe | 15m |
| Direction | long only |
| Leverage | 3 |
| Trend | SMA(close, 60) |
| RSI | RSI(close, 2) |
| Entry signal | close > trend and rsi2 <= 3.0 |
| Entry levels | 0.003, 0.006, 0.009 below decision close |
| Entry validity | 4 full 15m bars after decision candle |
| Stop | 1.2% below weighted average entry |
| Exit signal | rsi2 >= 50.0 or held bars >= 48 |
| Cost assumption | maker entry, taker exit |
The strategy computes only once per 15m candle close. The decision input is the latest OKX history-candles row for ETH-USDT-SWAP, bar=15m, whose confirm flag is 1. The next cycle is skipped until that confirmed candle timestamp advances beyond state.signal.last_confirmed_candle_ts.
The decision candle is the latest confirmed 15m candle. Indicator inputs are all confirmed candles up to and including that decision candle. The open 15m candle, ticker price, and any candle row without confirm flag 1 are not indicator inputs.
Execution starts in the next 15m candle. If candle T closes and produces an entry signal, the three entry orders are created after T is confirmed and are eligible during candles T+1, T+2, T+3, and T+4. At the first scheduler tick after T+4 closes, any unfilled entry orders from that signal are canceled.
The minimum live runner cadence is one poll per minute. Signal calculation remains candle-bound; the extra polling only reconciles order state, partial fills, stop reach, and expiry.
Entry is exactly three isolated long limit orders with post_only order type:
| Level | Limit price |
|---|---|
| L1 | decision_close * (1 - 0.003) |
| L2 | decision_close * (1 - 0.006) |
| L3 | decision_close * (1 - 0.009) |
Each level receives one third of planned_margin_usdt. Contract size is computed per level with the existing sizing rule shape:
slice_margin = planned_margin_usdt / 3
notional = slice_margin * 3
raw_contracts = notional / (limit_price * ct_val)
size = floor_to_lot_size(raw_contracts, lotSz)
If any slice is below minSz, the test margin is too small for this design and no live cycle should be started. The order-intent command should surface that as a hard configuration error before any submission command exists.
Order payload shape needed in the future implementation:
{
"instId": "ETH-USDT-SWAP",
"tdMode": "isolated",
"side": "buy",
"posSide": "long",
"ordType": "post_only",
"px": "<level_price>",
"sz": "<level_contract_size>",
"clOrdId": "ethrtwap15m-<signal_ts>-<level>"
}
Cancellation rules are direct:
post_only orders with taker orders.The live position is built from exchange fills, not from submitted order sizes.
For each fill:
fill_base_qty = fill_contracts * ct_val
fill_notional_usdt = fill_base_qty * fill_price
The tracked average entry is:
avg_entry_price = sum(fill_notional_usdt) / sum(fill_base_qty)
The tracked margin is:
margin_used = sum(fill_notional_usdt) / leverage
After every new fill, recompute:
stop_price = avg_entry_price * (1 - 0.012)
first_fill_ts and entry_candle_ts are set by the first fill only. Held bars are counted from entry_candle_ts to the latest confirmed candle. If only one or two levels fill, the strategy runs with that partial position and cancels the remaining unfilled levels at expiry or exit.
There are two exits.
Signal exit uses confirmed candles only. If the latest confirmed candle has rsi2 >= 50.0, or if held bars from entry_candle_ts reach 48, the runner cancels unfilled entries and closes the tracked long position.
Stop exit is price-driven. Once a position exists, the runner watches the live mark or last price against stop_price. If price reaches or crosses stop_price, it cancels unfilled entries and closes the tracked long position immediately.
Execution assumption for the first live design:
| Action | Order assumption | Reason |
|---|---|---|
| Entry | maker | post_only limit orders only |
| Signal exit | taker | exposure must be closed once the confirmed exit condition exists |
| Stop exit | taker | stop is a risk boundary, not a maker objective |
The robustness report's maker_taker accounting is therefore the right planning cost model for this first live design. Maker exit can be designed later as a separate strategy; it is not part of this minimal path.
Future close payload shape:
{
"instId": "ETH-USDT-SWAP",
"tdMode": "isolated",
"side": "sell",
"posSide": "long",
"ordType": "market",
"sz": "<tracked_filled_contracts>",
"reduceOnly": "true",
"clOrdId": "ethrtwap15m-exit-<position_id>"
}
Use a dedicated state file, for example state/eth_robust_twap_15m_live.json. It must be separate from paper_state.json because paper state has no exchange order lifecycle.
Required fields:
| Area | Fields |
|---|---|
| Identity | strategy_id, symbol, bar, td_mode, pos_side, leverage |
| Signal | last_confirmed_candle_ts, last_signal_candle_ts, trend_sma, rsi2, decision |
| Entry plan | decision_close, entry_offsets, entry_valid_bars, expires_after_candle_ts |
| Orders | client_order_id, okx_order_id, level, price, requested_size, filled_size, state, created_at, expires_at |
| Position | filled_contracts, filled_base_qty, filled_notional_usdt, avg_entry_price, margin_used, stop_price, first_fill_ts, entry_candle_ts |
| Risk | planned_margin_usdt, max_margin_usdt, account_isolation |
| Audit | append-only events, updated_at |
State transitions are single-owner:
idle -> entry_orders_open -> partial_or_full_position -> closing -> idle
The runner should not start a new signal while state is entry_orders_open, partial_or_full_position, or closing.
Use OKX demo first. For real funds, use a dedicated OKX subaccount. That is the clean way to avoid touching an existing ETH position.
If a subaccount is unavailable, the test account must have no existing ETH-USDT-SWAP long position before starting. The runner must query okx-account --symbol ETH-USDT-SWAP and require zero long size before any future live submit command is used. All test orders use deterministic client order ids with prefix ethrtwap15m-, so manual inspection can distinguish them from other account activity.
Minimum test sequence:
uv run python scripts/design_eth_robust_twap_live_plan.py.post_only payloads, rounded sizes, expiry timestamps, and stop price.planned_margin_usdt that passes minSz for all three levels.okx-account reports no tracked long exposure.No current command should be used for this test as-is. Existing okx-order is single-order and can submit market or plain limit orders, but it does not express the required three-level post_only lifecycle.
Current commands that are useful as inputs:
| Command | Use |
|---|---|
fetch-history |
confirmed candle source shape |
okx-account |
account balance and existing position inspection |
positions |
local paper positions only |
paper-order |
not suitable for this plan because it assumes immediate fills |
okx-order |
not suitable for this plan because it is a single-order submitter |
Minimum implementation path:
eth-robust-twap-signal, that fetches confirmed 15m candles and emits the next-cycle entry plan.eth-robust-twap-order-intent, that converts a signal into three post_only payloads and a state transition preview.Freqtrade should remain a comparison harness for this repo. This execution design should live in the existing OKX client and CLI path because it requires exchange order ids, client order ids, fill reconciliation, and explicit state transitions.
The added static check is:
uv run python scripts/design_eth_robust_twap_live_plan.py
It validates that this report and the structured JSON plan exist, include the required sections, and do not describe direct use of the current executable order call.