@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.
- package/bridge/gateway/event-parser.d.ts +6 -1
- package/bridge/gateway/event-parser.js +19 -2
- package/bridge/gateway/poller.d.ts +1 -0
- package/bridge/gateway/poller.js +14 -2
- package/bridge/providers/gateway.d.ts +22 -2
- package/bridge/providers/gateway.js +67 -9
- package/ccxt/binance-private.d.ts +21 -0
- package/ccxt/binance-private.js +132 -22
- package/ccxt/public-market-data-api.d.ts +14 -0
- package/ccxt/public-market-data-api.js +15 -1
- package/config/plugin-config-io.d.ts +13 -0
- package/config/plugin-config-io.js +15 -0
- package/config/tool-gate.js +3 -0
- package/exchange-adapter.d.ts +16 -0
- package/index.js +658 -83
- package/ingest/position-auto-capture.d.ts +68 -0
- package/ingest/position-auto-capture.js +321 -23
- package/ingest/position-decisions-client.d.ts +7 -2
- package/ingest/position-decisions-client.js +13 -3
- package/ingest/reconcile-db-vs-exchange.d.ts +39 -1
- package/ingest/reconcile-db-vs-exchange.js +66 -10
- package/lifecycle/trading-operation-lock.d.ts +17 -0
- package/lifecycle/trading-operation-lock.js +14 -0
- package/live/bracket-id.d.ts +2 -3
- package/live/bracket-id.js +22 -9
- package/live/fill-price.d.ts +13 -0
- package/live/fill-price.js +37 -0
- package/live/live-adapter.d.ts +57 -2
- package/live/live-adapter.js +290 -49
- package/live/local-signal-service.js +11 -6
- package/live/local-strategy-evaluator.js +4 -0
- package/live/position-state-store.d.ts +4 -0
- package/live/proposal-decision-listener.d.ts +6 -0
- package/live/proposal-decision-listener.js +4 -0
- package/live/stop-watcher.d.ts +35 -2
- package/live/stop-watcher.js +63 -3
- package/onboarding/runtime.d.ts +19 -0
- package/onboarding/runtime.js +34 -3
- package/openclaw.plugin.json +1 -0
- package/package.json +2 -2
- package/portfolio/reentry-tracker.d.ts +36 -0
- package/portfolio/reentry-tracker.js +127 -0
- package/portfolio/wave9-admission.d.ts +67 -0
- package/portfolio/wave9-admission.js +262 -0
- package/portfolio/wave9-policy.d.ts +36 -0
- package/portfolio/wave9-policy.js +183 -0
- package/signals/conditions/registry.js +61 -2
- package/signals/strategy-adapter.js +17 -7
- package/simulator/exchange-simulator.d.ts +12 -0
- package/simulator/exchange-simulator.js +78 -3
- package/simulator/fill-engine.js +5 -1
- package/simulator/types.d.ts +10 -1
- package/skills/reefclaw/SKILL.md +2 -0
- package/strategy/evaluator.d.ts +3 -0
- package/strategy/evaluator.js +5 -0
- package/tools/assessment-validation.d.ts +23 -0
- package/tools/assessment-validation.js +58 -0
- package/tools/attach-brackets.d.ts +7 -2
- package/tools/attach-brackets.js +201 -0
- package/tools/audit-bracket-protection.js +157 -1
- package/tools/bracket-control.d.ts +12 -0
- package/tools/bracket-control.js +35 -0
- package/tools/cancel-all-orders.d.ts +2 -0
- package/tools/cancel-all-orders.js +4 -1
- package/tools/cancel-order.d.ts +4 -0
- package/tools/cancel-order.js +49 -3
- package/tools/close-position.d.ts +23 -0
- package/tools/close-position.js +286 -13
- package/tools/create-order.d.ts +28 -0
- package/tools/create-order.js +1390 -190
- package/tools/get-analytics.js +2 -2
- package/tools/get-basis.js +2 -2
- package/tools/get-cascade-risk.js +2 -2
- package/tools/get-crypto-metrics.js +14 -4
- package/tools/get-cvd.js +2 -2
- package/tools/get-divergences.js +2 -2
- package/tools/get-funding-context.js +2 -2
- package/tools/get-liquidation-levels.js +2 -2
- package/tools/get-liquidation-pulse.js +2 -2
- package/tools/get-pattern-scan.js +2 -2
- package/tools/get-regime.js +2 -2
- package/tools/get-resting-liquidity.js +2 -2
- package/tools/get-risk-scenario.js +2 -2
- package/tools/get-session-review.js +2 -2
- package/tools/get-setup-detail.js +21 -2
- package/tools/get-signals.js +2 -2
- package/tools/get-sizing.js +2 -2
- package/tools/get-trade-feedback.js +2 -2
- package/tools/get-trade-flow.js +2 -2
- package/tools/get-volume-profile.js +2 -2
- package/tools/get-wave9-status.d.ts +127 -0
- package/tools/get-wave9-status.js +796 -0
- package/tools/intel-api.d.ts +20 -0
- package/tools/intel-api.js +67 -0
- package/tools/intel-cache.d.ts +1 -1
- package/tools/intel-cache.js +20 -5
- package/tools/list-strategies.d.ts +11 -1
- package/tools/list-strategies.js +17 -0
- package/tools/modify-stop.d.ts +4 -0
- package/tools/modify-stop.js +63 -24
- package/tools/modify-target.d.ts +4 -0
- package/tools/modify-target.js +62 -23
- package/tools/scan-pairs.d.ts +4 -0
- package/tools/scan-pairs.js +22 -8
- package/tools/toggle-strategy.js +7 -0
- package/types.d.ts +5 -0
- package/venues/hyperliquid/hl-balance.d.ts +116 -0
- package/venues/hyperliquid/hl-balance.js +145 -0
- package/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
- package/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
- package/venues/hyperliquid/hl-brackets.d.ts +102 -0
- package/venues/hyperliquid/hl-brackets.js +172 -0
- package/venues/hyperliquid/hl-cloid.d.ts +22 -0
- package/venues/hyperliquid/hl-cloid.js +82 -0
- package/venues/hyperliquid/hl-info-cache.d.ts +46 -0
- package/venues/hyperliquid/hl-info-cache.js +125 -0
- package/venues/hyperliquid/hl-live-adapter.d.ts +146 -0
- package/venues/hyperliquid/hl-live-adapter.js +728 -0
- package/venues/hyperliquid/hl-precision.d.ts +61 -0
- package/venues/hyperliquid/hl-precision.js +176 -0
- package/venues/hyperliquid/hl-private.d.ts +88 -0
- package/venues/hyperliquid/hl-private.js +357 -0
- package/venues/hyperliquid/hl-public.d.ts +31 -4
- package/venues/hyperliquid/hl-public.js +163 -12
- package/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
- package/venues/hyperliquid/hl-rate-gate.js +220 -0
- package/venues/hyperliquid/hl-user-stream.d.ts +90 -0
- package/venues/hyperliquid/hl-user-stream.js +220 -0
- package/venues/registry.d.ts +23 -9
- package/venues/registry.js +12 -13
- package/venues/symbols.d.ts +43 -0
- package/venues/symbols.js +107 -0
- package/wave9/live-account-capture.d.ts +67 -0
- package/wave9/live-account-capture.js +435 -0
- package/wave9/live-autonomous-protection.d.ts +39 -0
- package/wave9/live-autonomous-protection.js +112 -0
- package/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
- package/wave9/live-durable-reconciliation-scheduler.js +115 -0
- package/wave9/live-execution-ledger.d.ts +107 -0
- package/wave9/live-execution-ledger.js +498 -0
- package/wave9/live-position-confirmation.d.ts +18 -0
- package/wave9/live-position-confirmation.js +111 -0
- package/wave9/live-residual-protection.d.ts +18 -0
- package/wave9/live-residual-protection.js +250 -0
- package/wave9/live-startup-reconciliation.d.ts +38 -0
- package/wave9/live-startup-reconciliation.js +454 -0
- package/wave9/live-symbol-ownership.d.ts +20 -0
- package/wave9/live-symbol-ownership.js +132 -0
- package/wave9/paper-admission-guard.d.ts +199 -0
- package/wave9/paper-admission-guard.js +650 -0
- package/wave9/usdm-evidence-provider.d.ts +42 -0
- package/wave9/usdm-evidence-provider.js +133 -0
package/tools/close-position.js
CHANGED
|
@@ -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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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
|
}
|
package/tools/create-order.d.ts
CHANGED
|
@@ -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 {};
|