@reefclaw/openclaw-plugin 0.1.5 → 0.1.7

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.
Files changed (152) hide show
  1. package/bridge/gateway/event-parser.d.ts +6 -1
  2. package/bridge/gateway/event-parser.js +19 -2
  3. package/bridge/gateway/poller.d.ts +1 -0
  4. package/bridge/gateway/poller.js +14 -2
  5. package/bridge/providers/gateway.d.ts +22 -2
  6. package/bridge/providers/gateway.js +67 -9
  7. package/ccxt/binance-private.d.ts +21 -0
  8. package/ccxt/binance-private.js +132 -22
  9. package/ccxt/public-market-data-api.d.ts +14 -0
  10. package/ccxt/public-market-data-api.js +15 -1
  11. package/config/plugin-config-io.d.ts +13 -0
  12. package/config/plugin-config-io.js +15 -0
  13. package/config/tool-gate.js +3 -0
  14. package/exchange-adapter.d.ts +16 -0
  15. package/index.js +658 -83
  16. package/ingest/position-auto-capture.d.ts +68 -0
  17. package/ingest/position-auto-capture.js +321 -23
  18. package/ingest/position-decisions-client.d.ts +7 -2
  19. package/ingest/position-decisions-client.js +13 -3
  20. package/ingest/reconcile-db-vs-exchange.d.ts +39 -1
  21. package/ingest/reconcile-db-vs-exchange.js +66 -10
  22. package/lifecycle/trading-operation-lock.d.ts +17 -0
  23. package/lifecycle/trading-operation-lock.js +14 -0
  24. package/live/bracket-id.d.ts +2 -3
  25. package/live/bracket-id.js +22 -9
  26. package/live/fill-price.d.ts +13 -0
  27. package/live/fill-price.js +37 -0
  28. package/live/live-adapter.d.ts +57 -2
  29. package/live/live-adapter.js +290 -49
  30. package/live/local-signal-service.js +11 -6
  31. package/live/local-strategy-evaluator.js +4 -0
  32. package/live/position-state-store.d.ts +4 -0
  33. package/live/proposal-decision-listener.d.ts +6 -0
  34. package/live/proposal-decision-listener.js +4 -0
  35. package/live/stop-watcher.d.ts +35 -2
  36. package/live/stop-watcher.js +63 -3
  37. package/onboarding/runtime.d.ts +19 -0
  38. package/onboarding/runtime.js +34 -3
  39. package/openclaw.plugin.json +1 -0
  40. package/package.json +2 -2
  41. package/portfolio/reentry-tracker.d.ts +36 -0
  42. package/portfolio/reentry-tracker.js +127 -0
  43. package/portfolio/wave9-admission.d.ts +67 -0
  44. package/portfolio/wave9-admission.js +262 -0
  45. package/portfolio/wave9-policy.d.ts +36 -0
  46. package/portfolio/wave9-policy.js +183 -0
  47. package/signals/conditions/registry.js +61 -2
  48. package/signals/strategy-adapter.js +17 -7
  49. package/simulator/exchange-simulator.d.ts +12 -0
  50. package/simulator/exchange-simulator.js +78 -3
  51. package/simulator/fill-engine.js +5 -1
  52. package/simulator/types.d.ts +10 -1
  53. package/skills/reefclaw/SKILL.md +2 -0
  54. package/strategy/evaluator.d.ts +3 -0
  55. package/strategy/evaluator.js +5 -0
  56. package/tools/assessment-validation.d.ts +23 -0
  57. package/tools/assessment-validation.js +58 -0
  58. package/tools/attach-brackets.d.ts +7 -2
  59. package/tools/attach-brackets.js +201 -0
  60. package/tools/audit-bracket-protection.js +157 -1
  61. package/tools/bracket-control.d.ts +12 -0
  62. package/tools/bracket-control.js +35 -0
  63. package/tools/cancel-all-orders.d.ts +2 -0
  64. package/tools/cancel-all-orders.js +4 -1
  65. package/tools/cancel-order.d.ts +4 -0
  66. package/tools/cancel-order.js +49 -3
  67. package/tools/close-position.d.ts +23 -0
  68. package/tools/close-position.js +286 -13
  69. package/tools/create-order.d.ts +28 -0
  70. package/tools/create-order.js +1390 -190
  71. package/tools/get-analytics.js +2 -2
  72. package/tools/get-basis.js +2 -2
  73. package/tools/get-cascade-risk.js +2 -2
  74. package/tools/get-crypto-metrics.js +14 -4
  75. package/tools/get-cvd.js +2 -2
  76. package/tools/get-divergences.js +2 -2
  77. package/tools/get-funding-context.js +2 -2
  78. package/tools/get-liquidation-levels.js +2 -2
  79. package/tools/get-liquidation-pulse.js +2 -2
  80. package/tools/get-pattern-scan.js +2 -2
  81. package/tools/get-regime.js +2 -2
  82. package/tools/get-resting-liquidity.js +2 -2
  83. package/tools/get-risk-scenario.js +2 -2
  84. package/tools/get-session-review.js +2 -2
  85. package/tools/get-setup-detail.js +21 -2
  86. package/tools/get-signals.js +2 -2
  87. package/tools/get-sizing.js +2 -2
  88. package/tools/get-trade-feedback.js +2 -2
  89. package/tools/get-trade-flow.js +2 -2
  90. package/tools/get-volume-profile.js +2 -2
  91. package/tools/get-wave9-status.d.ts +127 -0
  92. package/tools/get-wave9-status.js +796 -0
  93. package/tools/intel-api.d.ts +20 -0
  94. package/tools/intel-api.js +67 -0
  95. package/tools/intel-cache.d.ts +1 -1
  96. package/tools/intel-cache.js +20 -5
  97. package/tools/list-strategies.d.ts +11 -1
  98. package/tools/list-strategies.js +17 -0
  99. package/tools/modify-stop.d.ts +4 -0
  100. package/tools/modify-stop.js +63 -24
  101. package/tools/modify-target.d.ts +4 -0
  102. package/tools/modify-target.js +62 -23
  103. package/tools/scan-pairs.d.ts +4 -0
  104. package/tools/scan-pairs.js +22 -8
  105. package/tools/toggle-strategy.js +7 -0
  106. package/types.d.ts +5 -0
  107. package/venues/hyperliquid/hl-balance.d.ts +116 -0
  108. package/venues/hyperliquid/hl-balance.js +145 -0
  109. package/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
  110. package/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
  111. package/venues/hyperliquid/hl-brackets.d.ts +102 -0
  112. package/venues/hyperliquid/hl-brackets.js +172 -0
  113. package/venues/hyperliquid/hl-cloid.d.ts +22 -0
  114. package/venues/hyperliquid/hl-cloid.js +82 -0
  115. package/venues/hyperliquid/hl-info-cache.d.ts +46 -0
  116. package/venues/hyperliquid/hl-info-cache.js +125 -0
  117. package/venues/hyperliquid/hl-live-adapter.d.ts +146 -0
  118. package/venues/hyperliquid/hl-live-adapter.js +728 -0
  119. package/venues/hyperliquid/hl-precision.d.ts +61 -0
  120. package/venues/hyperliquid/hl-precision.js +176 -0
  121. package/venues/hyperliquid/hl-private.d.ts +88 -0
  122. package/venues/hyperliquid/hl-private.js +357 -0
  123. package/venues/hyperliquid/hl-public.d.ts +31 -4
  124. package/venues/hyperliquid/hl-public.js +163 -12
  125. package/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
  126. package/venues/hyperliquid/hl-rate-gate.js +220 -0
  127. package/venues/hyperliquid/hl-user-stream.d.ts +90 -0
  128. package/venues/hyperliquid/hl-user-stream.js +220 -0
  129. package/venues/registry.d.ts +23 -9
  130. package/venues/registry.js +12 -13
  131. package/venues/symbols.d.ts +43 -0
  132. package/venues/symbols.js +107 -0
  133. package/wave9/live-account-capture.d.ts +67 -0
  134. package/wave9/live-account-capture.js +435 -0
  135. package/wave9/live-autonomous-protection.d.ts +39 -0
  136. package/wave9/live-autonomous-protection.js +112 -0
  137. package/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
  138. package/wave9/live-durable-reconciliation-scheduler.js +115 -0
  139. package/wave9/live-execution-ledger.d.ts +107 -0
  140. package/wave9/live-execution-ledger.js +498 -0
  141. package/wave9/live-position-confirmation.d.ts +18 -0
  142. package/wave9/live-position-confirmation.js +111 -0
  143. package/wave9/live-residual-protection.d.ts +18 -0
  144. package/wave9/live-residual-protection.js +250 -0
  145. package/wave9/live-startup-reconciliation.d.ts +38 -0
  146. package/wave9/live-startup-reconciliation.js +454 -0
  147. package/wave9/live-symbol-ownership.d.ts +20 -0
  148. package/wave9/live-symbol-ownership.js +132 -0
  149. package/wave9/paper-admission-guard.d.ts +199 -0
  150. package/wave9/paper-admission-guard.js +650 -0
  151. package/wave9/usdm-evidence-provider.d.ts +42 -0
  152. package/wave9/usdm-evidence-provider.js +133 -0
@@ -26,7 +26,18 @@ import { onClosePositionFilled } from '../ingest/position-auto-capture.js';
26
26
  import { evaluateExitGate, } from './exit-gate.js';
27
27
  import { getRecentAuditVerdictHistory } from './audit-bracket-protection.js';
28
28
  import { loadExitGateMode } from '../config/position-review-config.js';
29
+ import { confirmWave9LivePositionFlat } from '../wave9/live-position-confirmation.js';
30
+ import { reprotectWave9LiveResidual, } from '../wave9/live-residual-protection.js';
31
+ import { captureWave9PaperPositionFingerprint, isWave9ManagedPosition, WAVE9_MECHANICAL_EXIT_REASON, } from '../wave9/paper-admission-guard.js';
29
32
  const TAG = 'close-position';
33
+ function currentWave9ExecutionMode(deps) {
34
+ const runtimeMode = deps.wave9TradingModeCheck?.() ?? deps.adapter.mode;
35
+ if (!deps.adapter.isLive && deps.adapter.mode === 'PAPER' && runtimeMode === 'PAPER')
36
+ return 'PAPER';
37
+ if (deps.adapter.isLive && deps.adapter.mode === 'LIVE' && runtimeMode === 'LIVE')
38
+ return 'LIVE';
39
+ return null;
40
+ }
30
41
  // Bracket-integrity circuit-breaker. Counts SUCCESSFUL close_position calls
31
42
  // with reason='bracket_integrity' in a rolling 60-min window. When the 3rd
32
43
  // such close is attempted, reject — repeated bracket-integrity flushes in
@@ -40,10 +51,131 @@ const BRACKET_INTEGRITY_LIMIT = 3;
40
51
  let bracketIntegrityCloses = []; // timestamps of successful closes
41
52
  export async function closePositionTool(args, deps) {
42
53
  // Schema gate FIRST — reject before touching the exchange.
43
- const v = validateClosePosition(args);
44
- if (!v.ok) {
45
- logger.warn(TAG, `close_position rejected: ${v.error} (symbol=${args.symbol}, reason=${args.reason ?? 'missing'})`);
46
- return { error: v.error ?? 'Invalid close_position arguments.' };
54
+ const wave9ExitClaimed = args.reason !== 'operator_command' && (args.reason === WAVE9_MECHANICAL_EXIT_REASON
55
+ || args.wave9_exit_token !== undefined
56
+ || args.candidate_id !== undefined);
57
+ if (wave9ExitClaimed && deps.wave9VenueCheck?.() !== 'binance') {
58
+ return { error: 'Wave 9 reversal close refused: execution is pinned to Binance USD-M futures.' };
59
+ }
60
+ if (wave9ExitClaimed && !deps.operationLock) {
61
+ return { error: 'Wave 9 reversal close refused: process-local trading operation lock is unavailable.' };
62
+ }
63
+ const initialWave9Mode = wave9ExitClaimed ? currentWave9ExecutionMode(deps) : null;
64
+ let wave9Lease;
65
+ if (wave9ExitClaimed) {
66
+ if (!initialWave9Mode) {
67
+ return {
68
+ error: 'Wave 9 reversal close refused: capability execution requires an exact PAPER/paper-adapter ' +
69
+ 'or LIVE/live-adapter runtime match.',
70
+ };
71
+ }
72
+ if (args.reason !== WAVE9_MECHANICAL_EXIT_REASON
73
+ || !args.wave9_exit_token
74
+ || !args.candidate_id
75
+ || !deps.wave9AdmissionGuard) {
76
+ return { error: 'Wave 9 reversal close requires its exact reason, candidate identity, and short-lived exit token.' };
77
+ }
78
+ const executionCanonical = args.symbol.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
79
+ if (!executionCanonical.endsWith('USDT') || executionCanonical.length <= 4) {
80
+ return { error: `Wave 9 reversal close refused: unsupported execution symbol ${args.symbol}.` };
81
+ }
82
+ if (initialWave9Mode === 'LIVE' && (!deps.captureWave9PositionFingerprint || !deps.wave9LiveLedger)) {
83
+ return {
84
+ error: 'Wave 9 live reversal close refused: authoritative live position capture and the durable ' +
85
+ 'execution ledger are required.',
86
+ };
87
+ }
88
+ args = { ...args, symbol: `${executionCanonical.slice(0, -4)}/USDT:USDT` };
89
+ try {
90
+ wave9Lease = await deps.operationLock.withAccountLock(`wave9-begin-close:${args.candidate_id ?? args.symbol}`, async () => {
91
+ if (currentWave9ExecutionMode(deps) !== initialWave9Mode) {
92
+ throw new Error('adapter or trading mode changed before Wave 9 reversal began');
93
+ }
94
+ let position;
95
+ let positionFingerprint;
96
+ if (initialWave9Mode === 'LIVE') {
97
+ const capture = await deps.captureWave9PositionFingerprint(args.symbol);
98
+ position = capture.position;
99
+ positionFingerprint = capture.fingerprint;
100
+ }
101
+ else {
102
+ deps.refreshPaperState?.();
103
+ const positions = await deps.adapter.getPositionsOrNull();
104
+ if (positions === null)
105
+ throw new Error('paper position snapshot unavailable');
106
+ const canonical = (value) => value.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
107
+ const matches = positions.filter((candidate) => canonical(candidate.symbol) === canonical(args.symbol));
108
+ if (matches.length !== 1)
109
+ throw new Error('exactly one matching held position is required');
110
+ position = matches[0];
111
+ positionFingerprint = captureWave9PaperPositionFingerprint(deps.adapter, args.symbol).fingerprint;
112
+ }
113
+ if (!isWave9ManagedPosition(position)
114
+ || position.candidateId !== args.candidate_id
115
+ || !position.missionId) {
116
+ throw new Error('held position lacks exact Wave 9 identity/geometry');
117
+ }
118
+ return deps.wave9AdmissionGuard.beginExit({
119
+ token: args.wave9_exit_token,
120
+ candidateId: args.candidate_id,
121
+ missionId: position.missionId,
122
+ symbol: args.symbol,
123
+ positionSide: position.side,
124
+ setupType: position.setupType,
125
+ reason: args.reason,
126
+ positionFingerprint,
127
+ tradingMode: initialWave9Mode,
128
+ });
129
+ });
130
+ }
131
+ catch (err) {
132
+ return { error: `Wave 9 reversal close refused: ${formatError(err)}` };
133
+ }
134
+ }
135
+ else {
136
+ // Preserve the generic tool contract: malformed calls are rejected before
137
+ // any adapter read. Wave 9 classification is an additional check only for
138
+ // already-valid, non-operator risk-reducing closes.
139
+ const v = validateClosePosition(args);
140
+ if (!v.ok) {
141
+ logger.warn(TAG, `close_position rejected: ${v.error} (symbol=${args.symbol}, reason=${args.reason ?? 'missing'})`);
142
+ return { error: v.error ?? 'Invalid close_position arguments.' };
143
+ }
144
+ if (args.reason !== 'operator_command'
145
+ && deps.adapter.isLive
146
+ && deps.checkWave9LiveSymbolOwnership
147
+ && !deps.operationLock) {
148
+ return {
149
+ error: 'Generic live close_position refused: the shared trading operation lock is required ' +
150
+ 'while Wave 9 LIVE ownership checks are enabled.',
151
+ };
152
+ }
153
+ // When a trusted position snapshot is available, do not route a held
154
+ // Wave 9 position through a generic thesis/risk/bracket reason. Snapshot
155
+ // failure stays fail-open because an ordinary risk-reducing close must not
156
+ // be trapped by extra strategy-lifecycle I/O. Operator remains unconditional.
157
+ if (args.reason !== 'operator_command') {
158
+ try {
159
+ const getTrusted = deps.adapter.getPositionsOrNull?.bind(deps.adapter);
160
+ const held = getTrusted ? await getTrusted() : undefined;
161
+ if (held) {
162
+ const canonical = (value) => value.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
163
+ if (held.some((position) => canonical(position.symbol) === canonical(args.symbol)
164
+ && isWave9ManagedPosition(position))) {
165
+ return {
166
+ error: `Wave 9 position close refused: call get_wave9_status and use reason=${WAVE9_MECHANICAL_EXIT_REASON} ` +
167
+ 'with its single-use exit token. Only an explicit operator_command bypasses the strategy exit policy.',
168
+ };
169
+ }
170
+ }
171
+ else if (held === null) {
172
+ logger.warn(TAG, 'Wave 9 generic-close classification unavailable; proceeding with legacy risk-reducing close semantics.');
173
+ }
174
+ }
175
+ catch (err) {
176
+ logger.warn(TAG, `Wave 9 generic-close classification failed (${formatError(err)}); proceeding with legacy risk-reducing close semantics.`);
177
+ }
178
+ }
47
179
  }
48
180
  // Exit-gate check — see docs/EXIT_GATE_DESIGN.md. The gate is orthogonal to
49
181
  // the bracket_integrity safeguard below; both can fire on the same call.
@@ -63,7 +195,7 @@ export async function closePositionTool(args, deps) {
63
195
  // snapshot was never constructed). See docs/EXIT_DISCIPLINE_BUILD_PLAN.md
64
196
  // Step 0a + docs/EXIT_GATE_DESIGN.md §5.
65
197
  let gateInputs = null;
66
- if (gateMode !== 'off') {
198
+ if (!wave9ExitClaimed && gateMode !== 'off') {
67
199
  gateInputs = await buildExitGateInputs(args, deps);
68
200
  gateVerdict = evaluateExitGate(gateInputs);
69
201
  logger.info(TAG, `exit_gate mode=${gateMode} ${gateVerdict.outcome} tier=${gateVerdict.tier} code=${gateVerdict.code} (symbol=${args.symbol}, reason=${args.reason})`);
@@ -80,13 +212,30 @@ export async function closePositionTool(args, deps) {
80
212
  }
81
213
  // In paper mode, fetch latest ticker + order book for realistic fills
82
214
  if (!deps.adapter.isLive) {
83
- const paperDeps = { binanceApi: deps.binanceApi, simulator: deps.adapter.getSimulator() };
84
- const [priceResult] = await Promise.all([
85
- fetchCurrentPrice(args.symbol, paperDeps),
86
- fetchOrderBook(args.symbol, paperDeps),
87
- ]);
88
- if (isError(priceResult))
89
- return priceResult;
215
+ try {
216
+ const paperDeps = { binanceApi: deps.binanceApi, simulator: deps.adapter.getSimulator() };
217
+ const [priceResult] = await Promise.all([
218
+ fetchCurrentPrice(args.symbol, paperDeps),
219
+ fetchOrderBook(args.symbol, paperDeps),
220
+ ]);
221
+ if (!isError(priceResult)) {
222
+ // The helper updates the simulator mark used by the actual close fill.
223
+ }
224
+ else {
225
+ if (wave9Lease) {
226
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'exit_market_data_unavailable', { retryable: true });
227
+ wave9Lease = undefined;
228
+ }
229
+ return priceResult;
230
+ }
231
+ }
232
+ catch (err) {
233
+ if (wave9Lease) {
234
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'exit_market_data_threw', { retryable: true });
235
+ wave9Lease = undefined;
236
+ }
237
+ return { error: `close_position market-data refresh failed: ${formatError(err)}` };
238
+ }
90
239
  }
91
240
  // Log the assessment so every agent close is auditable in the journal.
92
241
  // We log after validation (we've confirmed the shape is sound) and before
@@ -133,7 +282,127 @@ export async function closePositionTool(args, deps) {
133
282
  logger.info(TAG, `bracket_integrity safeguard: ${integrity.detail} — allowing close`);
134
283
  }
135
284
  try {
136
- const order = await deps.adapter.closePosition(args.symbol);
285
+ const close = async () => {
286
+ if (!wave9ExitClaimed && !deps.adapter.isLive && reason !== 'operator_command') {
287
+ deps.refreshPaperState?.();
288
+ const positions = await deps.adapter.getPositionsOrNull();
289
+ if (positions === null) {
290
+ // Preserve the existing risk-reduction contract: the extra
291
+ // classification read cannot trap an ordinary close.
292
+ logger.warn(TAG, 'Wave 9 final close classification unavailable; proceeding risk-reducing fail-open.');
293
+ }
294
+ else {
295
+ const canonical = (value) => value.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
296
+ const target = positions.find((position) => canonical(position.symbol) === canonical(args.symbol));
297
+ if (target && isWave9ManagedPosition(target)) {
298
+ throw new Error(`Generic close_position refused at mutation boundary: ${args.symbol} is now held by Wave 9.`);
299
+ }
300
+ }
301
+ }
302
+ if (!wave9ExitClaimed
303
+ && deps.adapter.isLive
304
+ && reason !== 'operator_command'
305
+ && deps.checkWave9LiveSymbolOwnership) {
306
+ let ownership;
307
+ try {
308
+ ownership = await deps.checkWave9LiveSymbolOwnership(args.symbol);
309
+ }
310
+ catch (err) {
311
+ throw new Error(`Generic close_position refused at mutation boundary: Wave 9 LIVE ownership ` +
312
+ `check failed for ${args.symbol} (${formatError(err)}).`);
313
+ }
314
+ if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
315
+ throw new Error(`Generic close_position refused at mutation boundary: ${args.symbol} has ` +
316
+ `${ownership.status} Wave 9 LIVE ownership (${ownership.reason}). ` +
317
+ 'Use get_wave9_status for a reversal authorization or operator_command for an emergency close.');
318
+ }
319
+ }
320
+ return wave9ExitClaimed
321
+ ? deps.adapter.closePosition(args.symbol, 'wave9_signal_reversal')
322
+ : deps.adapter.closePosition(args.symbol);
323
+ };
324
+ const closeWave9 = async () => {
325
+ if (!wave9Lease)
326
+ throw new Error('Wave 9 exit lease is unavailable at mutation boundary.');
327
+ if (currentWave9ExecutionMode(deps) !== initialWave9Mode) {
328
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'trading_mode_changed_before_exit_mutation', { retryable: true });
329
+ wave9Lease = undefined;
330
+ throw new Error('Wave 9 reversal close refused: adapter or trading mode changed before mutation.');
331
+ }
332
+ if (deps.wave9VenueCheck?.() !== 'binance') {
333
+ deps.wave9AdmissionGuard.abort(wave9Lease, 'execution_venue_changed_before_exit_mutation', { retryable: true });
334
+ wave9Lease = undefined;
335
+ throw new Error('Wave 9 reversal close refused: execution venue changed away from Binance before mutation.');
336
+ }
337
+ let currentFingerprint;
338
+ if (initialWave9Mode === 'LIVE') {
339
+ const capture = await deps.captureWave9PositionFingerprint(args.symbol);
340
+ if (!isWave9ManagedPosition(capture.position)
341
+ || capture.position.candidateId !== args.candidate_id
342
+ || !capture.position.missionId) {
343
+ throw new Error('held live position no longer matches its exact Wave 9 identity');
344
+ }
345
+ currentFingerprint = capture.fingerprint;
346
+ }
347
+ else {
348
+ deps.refreshPaperState?.();
349
+ currentFingerprint = captureWave9PaperPositionFingerprint(deps.adapter, args.symbol).fingerprint;
350
+ }
351
+ deps.wave9AdmissionGuard.revalidate(wave9Lease, currentFingerprint);
352
+ let order;
353
+ let closeError;
354
+ try {
355
+ order = await close();
356
+ }
357
+ catch (err) {
358
+ if (initialWave9Mode !== 'LIVE')
359
+ throw err;
360
+ closeError = formatError(err);
361
+ }
362
+ if (initialWave9Mode === 'LIVE') {
363
+ const flat = await confirmWave9LivePositionFlat(deps.adapter, args.symbol);
364
+ if (!flat.confirmed) {
365
+ let protection;
366
+ try {
367
+ protection = await (deps.reprotectWave9LiveResidual ?? reprotectWave9LiveResidual)(deps.adapter, deps.wave9LiveLedger, args.candidate_id, args.symbol);
368
+ }
369
+ catch (err) {
370
+ deps.wave9AdmissionGuard.abort(wave9Lease, `live_close_residual_reprotection_threw:${formatError(err)}`, { retryable: true });
371
+ wave9Lease = undefined;
372
+ throw new Error(`CRITICAL: Wave 9 LIVE reversal is not stably flat (${flat.detail}), and residual ` +
373
+ `re-protection threw (${formatError(err)}). Durable ownership remains OPEN. ` +
374
+ 'IMMEDIATE MANUAL INTERVENTION REQUIRED; a fresh exit authorization may retry.');
375
+ }
376
+ if (protection.status === 'protected') {
377
+ deps.wave9AdmissionGuard.abort(wave9Lease, `live_close_residual_reprotected:${protection.stopClientOrderId ?? 'exact_stop'}`, { retryable: true });
378
+ wave9Lease = undefined;
379
+ throw new Error(`Wave 9 LIVE reversal close${closeError ? ` threw (${closeError}) and` : ''} left ` +
380
+ `${protection.residualQuantity ?? 'a'} residual ` +
381
+ `contract position. Durable ownership remains OPEN and the exact frozen stop ` +
382
+ `${protection.stopPrice} is proven live; refresh get_wave9_status and use the ` +
383
+ 'fresh exit authorization to retry the reduce-only close.');
384
+ }
385
+ deps.wave9AdmissionGuard.abort(wave9Lease, `live_close_residual_requires_manual_intervention:${protection.detail}`, { retryable: true });
386
+ wave9Lease = undefined;
387
+ throw new Error(`CRITICAL: Wave 9 LIVE reversal close${closeError ? ` threw (${closeError}) and` : ''} ` +
388
+ `stable flat was not confirmed ` +
389
+ `(${flat.detail}). Durable ownership remains OPEN. ${protection.detail}`);
390
+ }
391
+ await deps.wave9LiveLedger.markClosed(args.candidate_id);
392
+ }
393
+ deps.wave9AdmissionGuard.commit(wave9Lease);
394
+ wave9Lease = undefined;
395
+ if (closeError) {
396
+ throw new Error(`Wave 9 LIVE reversal close acknowledgement failed (${closeError}), but stable flat was ` +
397
+ 'proven and durable ownership is CLOSED. Do not retry this candidate.');
398
+ }
399
+ return order;
400
+ };
401
+ const order = wave9ExitClaimed
402
+ ? await deps.operationLock.withAccountLock(`wave9-close-final:${args.symbol}`, closeWave9)
403
+ : reason !== 'operator_command' && deps.operationLock
404
+ ? await deps.operationLock.withAccountLock(`generic-close-final:${args.symbol}`, close)
405
+ : await close();
137
406
  // Successful close — record the bracket_integrity timestamp so the
138
407
  // circuit-breaker can see the trend on the next attempt.
139
408
  if (reason === 'bracket_integrity' && deps.adapter.isLive) {
@@ -228,6 +497,10 @@ export async function closePositionTool(args, deps) {
228
497
  return order;
229
498
  }
230
499
  catch (err) {
500
+ if (wave9Lease) {
501
+ deps.wave9AdmissionGuard.abort(wave9Lease, `exit_execution_failed:${formatError(err)}`, { retryable: initialWave9Mode === 'LIVE' });
502
+ wave9Lease = undefined;
503
+ }
231
504
  return { error: formatError(err) };
232
505
  }
233
506
  }
@@ -3,6 +3,13 @@ import type { IExchangeAdapter } from '../exchange-adapter.js';
3
3
  import type { CcxtOrder } from '../types.js';
4
4
  import { type AutoCaptureContext } from '../ingest/position-auto-capture.js';
5
5
  import type { ProposalManager } from '../live/proposal-manager.js';
6
+ import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
7
+ import { type Wave9LiveExecutionLedger } from '../wave9/live-execution-ledger.js';
8
+ import type { Wave9LiveStopConfirmationInput } from '../wave9/live-account-capture.js';
9
+ import { type Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
10
+ import { type Wave9LiveResidualProtectionResult } from '../wave9/live-residual-protection.js';
11
+ import { type Wave9PaperAdmissionGuard } from '../wave9/paper-admission-guard.js';
12
+ type Wave9LiveResidualProtector = (adapter: IExchangeAdapter, ledger: Wave9LiveExecutionLedger, candidateId: string, symbol: string) => Promise<Wave9LiveResidualProtectionResult>;
6
13
  export declare function createOrderTool(args: {
7
14
  symbol: string;
8
15
  side: string;
@@ -10,6 +17,10 @@ export declare function createOrderTool(args: {
10
17
  amount: number;
11
18
  price?: number;
12
19
  stopPrice?: number;
20
+ mission_id?: string;
21
+ /** Guarded Wave 9 identity/capability. Both are invalid on generic orders. */
22
+ candidate_id?: string;
23
+ wave9_admission_token?: string;
13
24
  setup_type?: string;
14
25
  thesis?: string;
15
26
  target_price?: number;
@@ -43,6 +54,22 @@ export declare function createOrderTool(args: {
43
54
  proposalManager?: ProposalManager;
44
55
  userId?: string;
45
56
  approvalMode?: 'off' | 'shadow' | 'per_trade';
57
+ wave9AdmissionGuard?: Wave9PaperAdmissionGuard;
58
+ wave9ActivationCheck?: () => Promise<{
59
+ available: boolean;
60
+ active: boolean;
61
+ }>;
62
+ refreshPaperState?: () => void;
63
+ wave9TradingModeCheck?: () => string;
64
+ wave9VenueCheck?: () => string;
65
+ captureWave9AccountFingerprint?: () => Promise<{
66
+ fingerprint: string;
67
+ }>;
68
+ confirmWave9LiveStop?: (input: Wave9LiveStopConfirmationInput) => Promise<boolean>;
69
+ wave9LiveLedger?: Wave9LiveExecutionLedger;
70
+ checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
71
+ reprotectWave9LiveResidual?: Wave9LiveResidualProtector;
72
+ operationLock?: TradingOperationLock;
46
73
  }): Promise<CcxtOrder | {
47
74
  error: string;
48
75
  } | {
@@ -52,3 +79,4 @@ export declare function createOrderTool(args: {
52
79
  setup_bucket: 'momentum' | 'mean_reversion';
53
80
  message: string;
54
81
  }>;
82
+ export {};