desic-okx-agent 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +90 -10
- package/README.md +76 -10
- package/dist/account/private-websocket.js +4 -4
- package/dist/account/private-websocket.js.map +1 -1
- package/dist/account/service.d.ts +12 -1
- package/dist/account/service.js +18 -0
- package/dist/account/service.js.map +1 -1
- package/dist/bars/rate-limiter.d.ts +18 -0
- package/dist/bars/rate-limiter.js +84 -0
- package/dist/bars/rate-limiter.js.map +1 -0
- package/dist/bars/schema.d.ts +36 -0
- package/dist/bars/schema.js +134 -0
- package/dist/bars/schema.js.map +1 -0
- package/dist/bars/service.d.ts +60 -0
- package/dist/bars/service.js +120 -0
- package/dist/bars/service.js.map +1 -0
- package/dist/bars/store.d.ts +105 -0
- package/dist/bars/store.js +415 -0
- package/dist/bars/store.js.map +1 -0
- package/dist/bars/timeframe.d.ts +40 -0
- package/dist/bars/timeframe.js +146 -0
- package/dist/bars/timeframe.js.map +1 -0
- package/dist/bars/types.d.ts +68 -0
- package/dist/bars/types.js +13 -0
- package/dist/bars/types.js.map +1 -0
- package/dist/cli/data-render.d.ts +37 -0
- package/dist/cli/data-render.js +143 -0
- package/dist/cli/data-render.js.map +1 -0
- package/dist/cli/doctor.js +7 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +757 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/live-render.d.ts +24 -0
- package/dist/cli/live-render.js +85 -0
- package/dist/cli/live-render.js.map +1 -0
- package/dist/cli/range.d.ts +28 -0
- package/dist/cli/range.js +63 -0
- package/dist/cli/range.js.map +1 -0
- package/dist/cli/render.js +3 -0
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/strategy-render.d.ts +36 -0
- package/dist/cli/strategy-render.js +391 -0
- package/dist/cli/strategy-render.js.map +1 -0
- package/dist/cli/width.d.ts +18 -0
- package/dist/cli/width.js +71 -0
- package/dist/cli/width.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.js +24 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/okx-client.d.ts +9 -1
- package/dist/core/okx-client.js +14 -5
- package/dist/core/okx-client.js.map +1 -1
- package/dist/i18n/locale.d.ts +24 -0
- package/dist/i18n/locale.js +65 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/messages.d.ts +333 -0
- package/dist/i18n/messages.js +660 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/live/account-snapshot.d.ts +30 -0
- package/dist/live/account-snapshot.js +130 -0
- package/dist/live/account-snapshot.js.map +1 -0
- package/dist/live/cutoff-queue.d.ts +42 -0
- package/dist/live/cutoff-queue.js +69 -0
- package/dist/live/cutoff-queue.js.map +1 -0
- package/dist/live/execution-key.d.ts +23 -0
- package/dist/live/execution-key.js +31 -0
- package/dist/live/execution-key.js.map +1 -0
- package/dist/live/failures.d.ts +37 -0
- package/dist/live/failures.js +57 -0
- package/dist/live/failures.js.map +1 -0
- package/dist/live/gates.d.ts +65 -0
- package/dist/live/gates.js +136 -0
- package/dist/live/gates.js.map +1 -0
- package/dist/live/loop.d.ts +56 -0
- package/dist/live/loop.js +197 -0
- package/dist/live/loop.js.map +1 -0
- package/dist/live/preconditions.d.ts +48 -0
- package/dist/live/preconditions.js +69 -0
- package/dist/live/preconditions.js.map +1 -0
- package/dist/live/reconcile.d.ts +46 -0
- package/dist/live/reconcile.js +104 -0
- package/dist/live/reconcile.js.map +1 -0
- package/dist/live/runner.d.ts +57 -0
- package/dist/live/runner.js +160 -0
- package/dist/live/runner.js.map +1 -0
- package/dist/live/schema.d.ts +18 -0
- package/dist/live/schema.js +91 -0
- package/dist/live/schema.js.map +1 -0
- package/dist/live/service.d.ts +144 -0
- package/dist/live/service.js +303 -0
- package/dist/live/service.js.map +1 -0
- package/dist/live/session.d.ts +85 -0
- package/dist/live/session.js +234 -0
- package/dist/live/session.js.map +1 -0
- package/dist/live/sizing.d.ts +62 -0
- package/dist/live/sizing.js +79 -0
- package/dist/live/sizing.js.map +1 -0
- package/dist/live/store.d.ts +123 -0
- package/dist/live/store.js +350 -0
- package/dist/live/store.js.map +1 -0
- package/dist/live/types.d.ts +82 -0
- package/dist/live/types.js +2 -0
- package/dist/live/types.js.map +1 -0
- package/dist/market/websocket.d.ts +16 -1
- package/dist/market/websocket.js +60 -5
- package/dist/market/websocket.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +15 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/network/connectivity.d.ts +9 -1
- package/dist/network/connectivity.js +28 -1
- package/dist/network/connectivity.js.map +1 -1
- package/dist/report/chart-script.d.ts +12 -0
- package/dist/report/chart-script.js +146 -0
- package/dist/report/chart-script.js.map +1 -0
- package/dist/report/compare-html.d.ts +8 -0
- package/dist/report/compare-html.js +254 -0
- package/dist/report/compare-html.js.map +1 -0
- package/dist/report/compare-script.d.ts +12 -0
- package/dist/report/compare-script.js +109 -0
- package/dist/report/compare-script.js.map +1 -0
- package/dist/report/compare.d.ts +61 -0
- package/dist/report/compare.js +205 -0
- package/dist/report/compare.js.map +1 -0
- package/dist/report/fetch.d.ts +20 -0
- package/dist/report/fetch.js +56 -0
- package/dist/report/fetch.js.map +1 -0
- package/dist/report/html.d.ts +54 -0
- package/dist/report/html.js +641 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/open.d.ts +42 -0
- package/dist/report/open.js +114 -0
- package/dist/report/open.js.map +1 -0
- package/dist/runtime/server.d.ts +16 -1
- package/dist/runtime/server.js +112 -9
- package/dist/runtime/server.js.map +1 -1
- package/dist/setup/installer.d.ts +1 -0
- package/dist/setup/installer.js +8 -0
- package/dist/setup/installer.js.map +1 -1
- package/dist/setup/wizard.js +19 -22
- package/dist/setup/wizard.js.map +1 -1
- package/dist/strategy/constants.d.ts +23 -0
- package/dist/strategy/constants.js +24 -0
- package/dist/strategy/constants.js.map +1 -0
- package/dist/strategy/environment.d.ts +52 -0
- package/dist/strategy/environment.js +187 -0
- package/dist/strategy/environment.js.map +1 -0
- package/dist/strategy/instrument.d.ts +29 -0
- package/dist/strategy/instrument.js +39 -0
- package/dist/strategy/instrument.js.map +1 -0
- package/dist/strategy/optimize.d.ts +73 -0
- package/dist/strategy/optimize.js +113 -0
- package/dist/strategy/optimize.js.map +1 -0
- package/dist/strategy/parameter-space.d.ts +59 -0
- package/dist/strategy/parameter-space.js +221 -0
- package/dist/strategy/parameter-space.js.map +1 -0
- package/dist/strategy/python-bridge.d.ts +24 -0
- package/dist/strategy/python-bridge.js +114 -0
- package/dist/strategy/python-bridge.js.map +1 -0
- package/dist/strategy/schema.d.ts +9 -0
- package/dist/strategy/schema.js +91 -0
- package/dist/strategy/schema.js.map +1 -0
- package/dist/strategy/service.d.ts +138 -0
- package/dist/strategy/service.js +745 -0
- package/dist/strategy/service.js.map +1 -0
- package/dist/strategy/settings.d.ts +162 -0
- package/dist/strategy/settings.js +243 -0
- package/dist/strategy/settings.js.map +1 -0
- package/dist/strategy/store.d.ts +96 -0
- package/dist/strategy/store.js +367 -0
- package/dist/strategy/store.js.map +1 -0
- package/dist/strategy/templates.d.ts +11 -0
- package/dist/strategy/templates.js +134 -0
- package/dist/strategy/templates.js.map +1 -0
- package/dist/strategy/types.d.ts +111 -0
- package/dist/strategy/types.js +2 -0
- package/dist/strategy/types.js.map +1 -0
- package/dist/tools/catalog.d.ts +16 -0
- package/dist/tools/catalog.js +118 -17
- package/dist/tools/catalog.js.map +1 -1
- package/dist/trade/service.d.ts +12 -0
- package/dist/trade/service.js +24 -7
- package/dist/trade/service.js.map +1 -1
- package/dist/tui/app.d.ts +23 -0
- package/dist/tui/app.js +322 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/commands.d.ts +70 -0
- package/dist/tui/commands.js +313 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/entries.d.ts +17 -0
- package/dist/tui/entries.js +24 -0
- package/dist/tui/entries.js.map +1 -0
- package/dist/tui/execute.d.ts +26 -0
- package/dist/tui/execute.js +664 -0
- package/dist/tui/execute.js.map +1 -0
- package/dist/tui/history.d.ts +17 -0
- package/dist/tui/history.js +48 -0
- package/dist/tui/history.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.js +48 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/line-editor.d.ts +44 -0
- package/dist/tui/line-editor.js +98 -0
- package/dist/tui/line-editor.js.map +1 -0
- package/dist/tui/progress.d.ts +23 -0
- package/dist/tui/progress.js +46 -0
- package/dist/tui/progress.js.map +1 -0
- package/dist/tui/settings-editor.d.ts +18 -0
- package/dist/tui/settings-editor.js +115 -0
- package/dist/tui/settings-editor.js.map +1 -0
- package/docs/live-trading.md +455 -0
- package/docs/strategy-research.md +597 -0
- package/package.json +10 -1
- package/python/desic_strategy/__init__.py +34 -0
- package/python/desic_strategy/actions.py +158 -0
- package/python/desic_strategy/context.py +164 -0
- package/python/desic_strategy/engine.py +614 -0
- package/python/desic_strategy/indicators.py +159 -0
- package/python/desic_strategy/live.py +253 -0
- package/python/desic_strategy/policy.py +193 -0
- package/python/desic_strategy/portfolio.py +152 -0
- package/python/desic_strategy/report.py +319 -0
- package/python/desic_strategy/runner.py +574 -0
- package/python/desic_strategy/timeframe.py +150 -0
- package/python/main.py +18 -0
- package/skills/okx-live-trading/SKILL.md +117 -0
- package/skills/okx-live-trading/agents/openai.yaml +9 -0
- package/skills/okx-live-trading/references/lifecycle.md +128 -0
- package/skills/okx-strategy-research/SKILL.md +113 -0
- package/skills/okx-strategy-research/agents/openai.yaml +9 -0
- package/skills/okx-strategy-research/references/execution-semantics.md +107 -0
- package/skills/okx-strategy-research/references/field-traps.md +142 -0
- package/skills/okx-strategy-research/references/python-api.md +121 -0
- package/skills/okx-strategy-research/references/tools-and-data.md +192 -0
- package/skills/okx-trading/SKILL.md +11 -10
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Field traps
|
|
2
|
+
|
|
3
|
+
These are the mistakes that actually get made. Each one is rejected by the source
|
|
4
|
+
policy or the engine, so getting it right the first time saves a round trip.
|
|
5
|
+
|
|
6
|
+
## Never probe for a field name
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
# Rejected: getattr is unavailable to a strategy.
|
|
10
|
+
price = getattr(ctx.bar, "close", None)
|
|
11
|
+
|
|
12
|
+
# Correct.
|
|
13
|
+
price = ctx.bar.close
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Probing hides a protocol mismatch that should fail loudly. If a field does not
|
|
17
|
+
exist, the run should stop and say so, not silently fall back to a default and
|
|
18
|
+
produce plausible-looking nonsense.
|
|
19
|
+
|
|
20
|
+
The same applies to `setattr`, `dir`, `vars`, `globals`, `locals`, `eval`, `exec`,
|
|
21
|
+
`compile`, `__import__`, `open`, `input`, and any `__dunder__` access.
|
|
22
|
+
|
|
23
|
+
## `position` is a method with two arguments
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
# Wrong: not a property.
|
|
27
|
+
if ctx.portfolio.position:
|
|
28
|
+
|
|
29
|
+
# Wrong: side is required.
|
|
30
|
+
ctx.portfolio.position(ctx.instrument_id)
|
|
31
|
+
|
|
32
|
+
# Correct.
|
|
33
|
+
if ctx.portfolio.position(ctx.instrument_id, "long") is not None:
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## The size field is `quantity`
|
|
37
|
+
|
|
38
|
+
Not `contracts`, `size`, `contractCount`, or `qty`.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
position = ctx.portfolio.position(ctx.instrument_id, "long")
|
|
42
|
+
if position is not None:
|
|
43
|
+
held = position.quantity
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Decisions carry no size
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
# Wrong: no quantity argument exists.
|
|
50
|
+
ctx.open_long(2.0, "momentum")
|
|
51
|
+
|
|
52
|
+
# Correct: reason first, host decides size.
|
|
53
|
+
ctx.open_long("momentum")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## A limit price is never positional
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# Wrong: this function does not exist.
|
|
60
|
+
ctx.open_long_limit("entry", 67000.0)
|
|
61
|
+
|
|
62
|
+
# Wrong: the second positional argument is protection.
|
|
63
|
+
ctx.open_long("entry", 67000.0)
|
|
64
|
+
|
|
65
|
+
# Correct.
|
|
66
|
+
ctx.open_long("entry", execution=ctx.limit_order(67000.0))
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Indicators return None during warm-up
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
fast = ctx.indicators.ema(ctx.instrument_id, "1m", 20)
|
|
73
|
+
slow = ctx.indicators.ema(ctx.instrument_id, "1m", 60)
|
|
74
|
+
|
|
75
|
+
# Wrong: comparing None raises.
|
|
76
|
+
if fast > slow:
|
|
77
|
+
|
|
78
|
+
# Correct.
|
|
79
|
+
if fast is None or slow is None:
|
|
80
|
+
return ctx.no_action("indicators warming up")
|
|
81
|
+
if fast > slow:
|
|
82
|
+
...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
EMA needs `period` bars; ATR needs `period + 1`.
|
|
86
|
+
|
|
87
|
+
## Indicators only accept `1m`
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# Rejected: a higher-timeframe bucket can be revised before it confirms.
|
|
91
|
+
ctx.indicators.ema(ctx.instrument_id, "15m", 20)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Compute a higher-timeframe value from `ctx.market.bars(..., "15m", ...)` yourself,
|
|
95
|
+
or use a `1m` period covering an equivalent span.
|
|
96
|
+
|
|
97
|
+
## `ctx` is read-only
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# Rejected.
|
|
101
|
+
ctx.as_of_ms = 0
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Return exactly one decision
|
|
105
|
+
|
|
106
|
+
Every path through `on_bar` must return a decision object. Returning `None`, a
|
|
107
|
+
string, or a bare number is rejected. Returning two actions from one bar is not
|
|
108
|
+
expressible — emit one and act on the next bar.
|
|
109
|
+
|
|
110
|
+
## Reversal must be explicit
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
# Rejected while a long is open.
|
|
114
|
+
return ctx.open_short("flip")
|
|
115
|
+
|
|
116
|
+
# Correct: close first, reverse on a later bar.
|
|
117
|
+
if ctx.portfolio.position(ctx.instrument_id, "long") is not None:
|
|
118
|
+
return ctx.close_long("exiting before reversing")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## `cancel_order` needs a live id
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
orders = ctx.portfolio.open_orders
|
|
125
|
+
if orders:
|
|
126
|
+
return ctx.cancel_order(orders[0].id, "no longer wanted")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
An id that is not currently open is an error, not a no-op.
|
|
130
|
+
|
|
131
|
+
## The current bar is the last item
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
bars = ctx.market.bars(ctx.instrument_id, "1m", lookback=60)
|
|
135
|
+
|
|
136
|
+
# Includes the bar that just closed — a "prior range high" computed this way
|
|
137
|
+
# is contaminated by the current bar.
|
|
138
|
+
highest = max(bar.high for bar in bars)
|
|
139
|
+
|
|
140
|
+
# Correct: exclude the current bar.
|
|
141
|
+
highest = max(bar.high for bar in bars[:-1])
|
|
142
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Strategy API
|
|
2
|
+
|
|
3
|
+
Every object is immutable and bounded by `ctx.as_of_ms`. Field names are exactly
|
|
4
|
+
as written here.
|
|
5
|
+
|
|
6
|
+
## Handlers
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
def on_bar(ctx): # required, runs after each confirmed 1m close
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
def on_start(ctx): # optional, initialization only, must return ctx.no_action(...)
|
|
13
|
+
...
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Both are synchronous and take exactly one positional argument. Ordinary helper
|
|
17
|
+
functions may be defined freely; the host never calls them.
|
|
18
|
+
|
|
19
|
+
## Context
|
|
20
|
+
|
|
21
|
+
| Field | Meaning |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `ctx.as_of_ms` | Cutoff in Unix milliseconds. During `on_bar`, the active bar's `closeTimeMs`. |
|
|
24
|
+
| `ctx.instrument_id` | Active instrument, for example `BTC-USDT-SWAP`. |
|
|
25
|
+
| `ctx.interval` | Event interval. Always `1m`. |
|
|
26
|
+
| `ctx.kind` | `start` or `bar`. |
|
|
27
|
+
| `ctx.bar` | The bar that just closed. Populated only during `on_bar`. |
|
|
28
|
+
| `ctx.params` | Immutable mapping of saved parameters. `ctx.params.get("fastPeriod", 20)`. |
|
|
29
|
+
|
|
30
|
+
## Market data
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
bars = ctx.market.bars(ctx.instrument_id, "1m", lookback=240)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`lookback` returns the final N items and must be a positive integer. Supported
|
|
37
|
+
intervals are `1m`, `3m`, `5m`, `15m`, `30m`, `1H`, `2H`, `4H`, `6H`, `12H`, and
|
|
38
|
+
`1D`, but a higher timeframe must be requested for the run before it is readable.
|
|
39
|
+
|
|
40
|
+
`Bar` fields: `openTimeMs`, `closeTimeMs`, `open`, `high`, `low`, `close`,
|
|
41
|
+
`volume`, `confirmed`.
|
|
42
|
+
|
|
43
|
+
All `1m` bars are confirmed. For higher intervals only the final item can be
|
|
44
|
+
`confirmed=False`; its OHLCV contains solely the minutes already known. Require
|
|
45
|
+
`bar.confirmed` before treating a higher-timeframe value as a confirmation.
|
|
46
|
+
|
|
47
|
+
## Indicators
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
fast = ctx.indicators.ema(ctx.instrument_id, "1m", 20)
|
|
51
|
+
atr = ctx.indicators.atr(ctx.instrument_id, "1m", 14, offset=1)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Both accept only `"1m"`. `offset=0` is the current bar, `offset=1` the previous
|
|
55
|
+
one. EMA returns `None` until `period` bars exist; ATR until `period + 1`, because
|
|
56
|
+
true range needs a prior close. Always handle `None` explicitly.
|
|
57
|
+
|
|
58
|
+
## Portfolio
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
position = ctx.portfolio.position(ctx.instrument_id, "long") # Position or None
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`position(instrument_id, side)` is a **method** taking both arguments, not a
|
|
65
|
+
property. `side` is `"long"` or `"short"`. `ctx.position(...)` is an alias.
|
|
66
|
+
|
|
67
|
+
| Accessor | Meaning |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `ctx.portfolio.cash_usdt`, `equity_usdt` | Virtual cash and account equity. |
|
|
70
|
+
| `ctx.portfolio.used_margin_usdt`, `available_margin_usdt` | Virtual margin. |
|
|
71
|
+
| `ctx.portfolio.positions` | Tuple of open positions. |
|
|
72
|
+
| `ctx.portfolio.positions_for(instrument_id)` | Positions for one instrument. |
|
|
73
|
+
| `ctx.portfolio.open_orders` | Resting limit orders. |
|
|
74
|
+
| `ctx.portfolio.recent_fills`, `trades` | Simulated fills and closed trades. |
|
|
75
|
+
|
|
76
|
+
`Position`: `instrumentId`, `side`, `quantity`, `averageEntryPrice`, `markPrice`,
|
|
77
|
+
`contractValue`, `notionalUsdt`, `usedMarginUsdt`, `leverage`,
|
|
78
|
+
`marginSafetyMultiplier`, `unrealizedPnlUsdt`, `entryFeeUsdt`, `stopLossPrice`,
|
|
79
|
+
`takeProfitPrice`, `openedAtMs`, `updatedAtMs`.
|
|
80
|
+
|
|
81
|
+
The size field is `quantity` — never `contracts`, `size`, or `contractCount`.
|
|
82
|
+
|
|
83
|
+
`OpenOrder`: `id`, `instrumentId`, `action`, `quantity`, `filledQuantity`,
|
|
84
|
+
`status`, `price`, `createdAtMs`.
|
|
85
|
+
|
|
86
|
+
`Trade`: `id`, `instrumentId`, `side`, `quantity`, `entryPrice`, `exitPrice`,
|
|
87
|
+
`usedMarginUsdt`, `leverage`, `realizedPnlUsdt`, `feesUsdt`, `openedAtMs`,
|
|
88
|
+
`closedAtMs`.
|
|
89
|
+
|
|
90
|
+
## Decisions
|
|
91
|
+
|
|
92
|
+
Return exactly one per bar:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
ctx.no_action(reason)
|
|
96
|
+
ctx.open_long(reason, protection=None, execution=None)
|
|
97
|
+
ctx.open_short(reason, protection=None, execution=None)
|
|
98
|
+
ctx.close_long(reason, execution=None)
|
|
99
|
+
ctx.close_short(reason, execution=None)
|
|
100
|
+
ctx.cancel_order(order_id, reason)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`reason` is required and is the first positional argument. Every other argument
|
|
104
|
+
must be passed by keyword.
|
|
105
|
+
|
|
106
|
+
Opening and closing decisions carry **no size**. The host derives a legal
|
|
107
|
+
contract count from its budget and the instrument's rules.
|
|
108
|
+
|
|
109
|
+
`execution` defaults to `ctx.market_order()`. The only alternative is
|
|
110
|
+
`ctx.limit_order(price)`. There is no `open_long_limit`.
|
|
111
|
+
|
|
112
|
+
`protection` accompanies an opening decision only, as a mapping of absolute
|
|
113
|
+
prices:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
ctx.open_long("breakout", protection={"stopLossPrice": 67000.0, "takeProfitPrice": 70000.0})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`cancel_order` must name an id currently in `ctx.portfolio.open_orders`.
|
|
120
|
+
|
|
121
|
+
Reversal is explicit: close the current side before opening the opposite one.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Tools and data
|
|
2
|
+
|
|
3
|
+
## Data is one-minute only
|
|
4
|
+
|
|
5
|
+
Only confirmed one-minute bars are stored. Every higher timeframe is derived from
|
|
6
|
+
them at read time, which is why a backtest and a live evaluation can never
|
|
7
|
+
disagree about where a bucket starts.
|
|
8
|
+
|
|
9
|
+
There is no `bar` parameter on any data tool.
|
|
10
|
+
|
|
11
|
+
## Tools
|
|
12
|
+
|
|
13
|
+
| Tool | Use |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| `data_list_instruments` | Every instrument with local history and its covered range. |
|
|
16
|
+
| `data_coverage` | One instrument's range, bar count, and gap list. |
|
|
17
|
+
| `data_download` | Download or repair an exact range. |
|
|
18
|
+
| `data_download_progress` | Progress of a running download, for a progress display. |
|
|
19
|
+
| `strategy_settings` | Read the assumptions every backtest uses by default. |
|
|
20
|
+
| `strategy_settings_update` | Change saved assumptions. Pass only the keys to change. |
|
|
21
|
+
| `strategy_environment` | Whether Python is ready. |
|
|
22
|
+
| `strategy_environment_setup` | Create the venv and install packages. Prefer telling the user to run the CLI. |
|
|
23
|
+
| `strategy_validate_source` | Static policy check with line numbers. |
|
|
24
|
+
| `strategy_run_backtest` | Queue a run, returns `runId` immediately. |
|
|
25
|
+
| `strategy_run_optimize` | Queue a parameter search. Splits the window, ranks out of sample. |
|
|
26
|
+
| `strategy_get_optimization` | Ranked candidates with training and validation metrics side by side. |
|
|
27
|
+
| `strategy_get_run` | Status, progress, summary metrics. |
|
|
28
|
+
| `strategy_list_runs` | Recent runs, newest first. |
|
|
29
|
+
| `strategy_get_run_equity` | Paged equity curve. |
|
|
30
|
+
| `strategy_get_run_trades` | Paged closed trades. |
|
|
31
|
+
| `strategy_get_run_actions` | Paged decisions the strategy emitted. |
|
|
32
|
+
| `strategy_compare_runs` | Two or more runs side by side, with the assumptions that differ and comparability warnings. |
|
|
33
|
+
| `strategy_cancel_run` | Cancel a queued or running run. |
|
|
34
|
+
| `strategy_delete_run` | Delete a run and its stored series. Confirm with the user first. |
|
|
35
|
+
|
|
36
|
+
## Check coverage before backtesting
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
data_coverage { "instId": "BTC-USDT-SWAP" }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`missingCount` above zero means the range has holes. A backtest over a window
|
|
43
|
+
containing one fails outright, so repair it first:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
data_download { "instId": "BTC-USDT-SWAP", "days": 90 }
|
|
47
|
+
data_download { "instId": "BTC-USDT-SWAP", "fromMs": 1767225600000, "toMs": 1782950400000 }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`exhaustedBefore` means OKX has no data earlier than that point. Requesting more
|
|
51
|
+
history below it will not help; say so instead of retrying.
|
|
52
|
+
|
|
53
|
+
## Assumptions
|
|
54
|
+
|
|
55
|
+
Leverage, entry budget, fees, slippage, margin safety, preload bars, and
|
|
56
|
+
close-at-end are saved settings, readable with `strategy_settings`. A backtest
|
|
57
|
+
request may override any of them for one run.
|
|
58
|
+
|
|
59
|
+
State them when reporting a result: a return figure means nothing without the
|
|
60
|
+
leverage and costs that produced it, and two runs are comparable only when these
|
|
61
|
+
match. Every completed run records what it used, returned as `assumptions` from
|
|
62
|
+
`strategy_get_run`.
|
|
63
|
+
|
|
64
|
+
## Backtest windows
|
|
65
|
+
|
|
66
|
+
`fromMs`/`toMs` bound the **evaluation** range. `preloadBars` are loaded entirely
|
|
67
|
+
*before* it, as warm-up context, and are excluded from every reported statistic.
|
|
68
|
+
|
|
69
|
+
The evaluation end is always clamped to at least one hour behind now, because the
|
|
70
|
+
most recent minutes are the ones an exchange is most likely to revise.
|
|
71
|
+
|
|
72
|
+
Limits: 365 evaluation days, 600,000 total bars including preload, minimum 2
|
|
73
|
+
preload bars.
|
|
74
|
+
|
|
75
|
+
## Runs are asynchronous
|
|
76
|
+
|
|
77
|
+
`strategy_run_backtest` returns `{ runId, status: "queued" }`. Poll
|
|
78
|
+
`strategy_get_run` until `status` is `completed`, `failed`, or `cancelled`.
|
|
79
|
+
`progressPct` and `etaMs` are populated while running.
|
|
80
|
+
|
|
81
|
+
A returned call is not a finished run. Never report metrics without confirming
|
|
82
|
+
`status` is `completed`.
|
|
83
|
+
|
|
84
|
+
## Read details in pages
|
|
85
|
+
|
|
86
|
+
`strategy_get_run` carries summary metrics only. Equity, trades, and actions are
|
|
87
|
+
separate paged reads with `offset` and `limit`. A full curve is tens of thousands
|
|
88
|
+
of points; fetch only what supports the point you are making.
|
|
89
|
+
|
|
90
|
+
## Parameter search
|
|
91
|
+
|
|
92
|
+
`strategy_run_optimize` takes a backtest request plus a `space` and a `budget`:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
strategy_run_optimize {
|
|
96
|
+
"instId": "BTC-USDT-SWAP",
|
|
97
|
+
"source": "...",
|
|
98
|
+
"params": { "fastPeriod": 20, "slowPeriod": 60 },
|
|
99
|
+
"space": {
|
|
100
|
+
"fastPeriod": { "min": 5, "max": 50, "step": 5 },
|
|
101
|
+
"slowPeriod": { "min": 20, "max": 200, "step": 10 }
|
|
102
|
+
},
|
|
103
|
+
"budget": 100,
|
|
104
|
+
"days": 90
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Pass `params` with the strategy's own defaults. Every key in `space` is checked
|
|
109
|
+
against them, and a range naming a parameter the strategy never reads is rejected
|
|
110
|
+
— otherwise every candidate would behave identically and the winner would be
|
|
111
|
+
noise.
|
|
112
|
+
|
|
113
|
+
Limits: at most 5,000 combinations in the space and a budget of at most 1,000. A
|
|
114
|
+
budget below the widest parameter's value count is refused, because covering every
|
|
115
|
+
value would then be impossible.
|
|
116
|
+
|
|
117
|
+
The window is split **70/30**. Candidates are searched over the first segment and
|
|
118
|
+
**ranked by the second**, which they never saw. Both segments come from one bar
|
|
119
|
+
load, so they cannot disagree about the underlying data.
|
|
120
|
+
|
|
121
|
+
Read the result with `strategy_get_optimization`, which returns candidates in rank
|
|
122
|
+
order with `train` and `validation` metrics on each, plus a `verdict`:
|
|
123
|
+
|
|
124
|
+
| Verdict | Meaning |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `holds` | Profitable on the held-back segment, keeping a fair share of the training result. |
|
|
127
|
+
| `overfit` | Profitable while being chosen, unprofitable or badly decayed afterwards. |
|
|
128
|
+
| `weak` | Unprofitable on the held-back segment either way. |
|
|
129
|
+
| `failed` | The candidate errored; read its `error`. |
|
|
130
|
+
|
|
131
|
+
`rankBasis` names the formula behind `rankScore`. It is `calmar` when the
|
|
132
|
+
validation segment is long enough to annualize, `return_over_drawdown` when it is
|
|
133
|
+
not, and `return` when no candidate drew down. Quote it — the column is not always
|
|
134
|
+
the same quantity.
|
|
135
|
+
|
|
136
|
+
## Reporting a search
|
|
137
|
+
|
|
138
|
+
Quote the **validation** figures, not the training ones. The training numbers
|
|
139
|
+
describe performance over the bars that selected the parameters, so they are not
|
|
140
|
+
evidence and must never be presented as the result.
|
|
141
|
+
|
|
142
|
+
Show both columns anyway. The gap between them is the finding: a large training
|
|
143
|
+
return beside a weak validation return means the parameters fitted noise, and that
|
|
144
|
+
is worth saying plainly rather than reporting the best row as a success.
|
|
145
|
+
|
|
146
|
+
Say that the winner's validation figure is still optimistic. Ranking many
|
|
147
|
+
candidates by that score selects for a high one, so the split removes most of the
|
|
148
|
+
bias but not all of it, and the remainder grows with the budget. A clean estimate
|
|
149
|
+
needs a period later than the whole search window. Never present the leader's
|
|
150
|
+
validation return as an expected future return.
|
|
151
|
+
|
|
152
|
+
Prefer a candidate whose neighbours also hold over a single sharp optimum. A lone
|
|
153
|
+
spike surrounded by poor results is a coincidence in the data, not a setting.
|
|
154
|
+
|
|
155
|
+
To use a winning set, write it to `<strategy>.params.json` beside the strategy —
|
|
156
|
+
never edit the strategy source, which is the user's. Then run one ordinary
|
|
157
|
+
backtest with those parameters to confirm, and say that this confirmation still
|
|
158
|
+
covers the same period the search used.
|
|
159
|
+
|
|
160
|
+
## Comparing runs
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
strategy_compare_runs { "runIds": ["bt_abc123", "bt_def456"] }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Two to six runs. The response carries each run's metrics, the assumption keys whose
|
|
167
|
+
values differ, the ones every run shares, and a `warnings` array.
|
|
168
|
+
|
|
169
|
+
**Read `warnings` before the metrics and repeat them to the user.** They are the
|
|
170
|
+
reason a comparison may be meaningless:
|
|
171
|
+
|
|
172
|
+
| Warning code | Meaning |
|
|
173
|
+
| --- | --- |
|
|
174
|
+
| data_snapshot | The runs consumed different bars. Any difference may come from the data rather than the strategy. |
|
|
175
|
+
| instrument | Different instruments. Each return reflects its own market's move. |
|
|
176
|
+
| window | Different evaluation windows, so the totals span different amounts of time. |
|
|
177
|
+
| assumptions | Different leverage, fees, slippage, or budget. |
|
|
178
|
+
| incomplete | A run did not finish and has no metrics. |
|
|
179
|
+
|
|
180
|
+
A comparison is only evidence that one variant beats another when every run shares
|
|
181
|
+
one `dataSnapshotId`, one instrument, one window, and one set of costs. When it does
|
|
182
|
+
not, say which of those differed instead of naming a winner.
|
|
183
|
+
|
|
184
|
+
Equity curves are indexed to 100 at each run's own start, so different initial
|
|
185
|
+
equity does not change the shape. Comparing absolute equity between runs that
|
|
186
|
+
started from different balances says nothing.
|
|
187
|
+
|
|
188
|
+
## Reproducibility
|
|
189
|
+
|
|
190
|
+
Each run records a `dataSnapshotId` whose hash covers the exact bars consumed. Two
|
|
191
|
+
identical requests over unchanged data yield the same id and the same metrics. A
|
|
192
|
+
differing id means the underlying data changed — quote the id when comparing runs.
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: okx-trading
|
|
3
|
-
description: Precheck and execute direct OKX leverage, ordinary-order, algo-order, amend, cancel, and position-close operations. Use when the user explicitly requests an OKX trading action and has configured an API key with the required official permission.
|
|
3
|
+
description: Precheck and execute direct OKX perpetual-swap leverage, ordinary-order, algo-order, amend, cancel, and position-close operations. Use when the user explicitly requests an OKX perpetual trading action and has configured an API key with the required official permission.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# OKX Trading
|
|
7
7
|
|
|
8
|
-
1.
|
|
9
|
-
2.
|
|
10
|
-
3. Call `
|
|
11
|
-
4. Call `
|
|
12
|
-
5.
|
|
13
|
-
6.
|
|
14
|
-
7.
|
|
15
|
-
8.
|
|
16
|
-
9.
|
|
8
|
+
1. This workflow supports OKX perpetual swaps only. Require an `instId` ending in `-SWAP`; refuse spot, dated futures, options, and every other instrument type.
|
|
9
|
+
2. Identify the account alias, instrument, side, margin mode, position side, order type, price, and size from the user's request. Use the account environment detected by the Runtime; do not ask the user to classify the API key or invent missing trading intent.
|
|
10
|
+
3. Call `market_get_instrument`; treat `size` as contracts and respect `lotSz`, `minSz`, and `tickSz`.
|
|
11
|
+
4. Call `market_get_decision_snapshot`. Do not proceed when `consistent` is false.
|
|
12
|
+
5. Call `trade_evaluate_plan`, then `trade_precheck_order` for ordinary orders or `trade_precheck_algo_order` for strategy orders. Resolve every blocker before submitting.
|
|
13
|
+
6. Create a stable, unique `executionKey` for each intended mutation. Reuse it only when retrying the exact same intent.
|
|
14
|
+
7. Call the narrowest write tool that matches the request. Do not substitute batch cancellation or full position closure for a narrower action.
|
|
15
|
+
8. If a result is `AMBIGUOUS_WRITE`, inspect remote order state. Never retry with a new execution key until the original outcome is resolved.
|
|
16
|
+
9. Report the exchange environment, returned order identifiers, status, reconciliation state, and any warnings.
|
|
17
|
+
10. Never use or propose withdrawal, transfer, deposit, or API-key-management operations.
|