@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
@@ -17,6 +17,10 @@
17
17
  import { parseBracketCid } from '../live/bracket-id.js';
18
18
  import { normalizeBracketSymbol } from '../live/bracket-ledger.js';
19
19
  import { formatError, logger } from '../logger.js';
20
+ import { HyperliquidLiveAdapter } from '../venues/hyperliquid/hl-live-adapter.js';
21
+ import { isTerminalBracketState } from '../venues/hyperliquid/hl-bracket-coordinator.js';
22
+ import { parseHlBracketCloid } from '../venues/hyperliquid/hl-cloid.js';
23
+ import { isProtectiveHlOrder } from '../venues/hyperliquid/hl-brackets.js';
20
24
  const TAG = 'audit-bracket-protection';
21
25
  // Per-symbol verdict history for flicker detection. When audit verdicts
22
26
  // transition protected→unprotected→protected repeatedly inside a short
@@ -73,7 +77,12 @@ export async function auditBracketProtectionTool(_args, deps) {
73
77
  positions: entries,
74
78
  };
75
79
  }
76
- // Live mode.
80
+ // Live mode — venue dispatch (issue #209). The Binance-shaped path below
81
+ // reads algo-order fields HL doesn't have; on HL it produced permanent
82
+ // false "unprotected" verdicts (the spiral seed).
83
+ if (deps.adapter instanceof HyperliquidLiveAdapter) {
84
+ return auditHlLive(deps.adapter);
85
+ }
77
86
  const live = deps.adapter;
78
87
  const ledger = live.getBracketLedger?.() ?? null;
79
88
  const bracketsEnabled = ledger !== null;
@@ -525,3 +534,150 @@ function extractTriggerPrice(o) {
525
534
  }
526
535
  return undefined;
527
536
  }
537
+ /**
538
+ * Hyperliquid live audit (issue #209). Same contracts as the Binance path:
539
+ * null ≠ empty ({error} on an unavailable read — the agent skips the cycle and
540
+ * MUST NOT act), and an EMPTY open-orders set against a non-terminal ledger row
541
+ * WITH cids is unverifiable, never "unprotected" (HL has no zero-weight per-leg
542
+ * resolver, so the honest floor is the whole-audit {error}). External trigger
543
+ * legs count as protection (`manual_order_protecting`) via the HL order shape
544
+ * (isTrigger/reduceOnly/orderType — T-7 fields), mirroring the Binance rule
545
+ * that a manually-placed stop must never be reported as naked.
546
+ */
547
+ async function auditHlLive(adapter) {
548
+ const ledger = adapter.getHlBracketCoordinator().getLedger();
549
+ const positionsOrNull = await adapter.getPositionsOrNull();
550
+ if (positionsOrNull === null) {
551
+ return {
552
+ error: 'Exchange data unavailable (Hyperliquid fetch failed / rate-gated). Skipping audit ' +
553
+ 'this cycle — bracket state on the exchange is unchanged. Retry on next heartbeat.',
554
+ };
555
+ }
556
+ const activePositions = positionsOrNull.filter(p => Math.abs(Number(p.contracts ?? 0)) > 0);
557
+ if (activePositions.length === 0) {
558
+ return {
559
+ ok: true,
560
+ mode: 'live',
561
+ brackets_enabled: true,
562
+ total_positions: 0,
563
+ protected_count: 0,
564
+ unprotected_count: 0,
565
+ positions: [],
566
+ };
567
+ }
568
+ const entries = [];
569
+ for (const p of activePositions) {
570
+ let symbolOrders;
571
+ try {
572
+ symbolOrders = await adapter.getOpenOrders(p.symbol);
573
+ }
574
+ catch (err) {
575
+ const m = formatError(err);
576
+ logger.warn(TAG, `HL audit skipped — open orders unavailable for ${p.symbol}: ${m}`);
577
+ return {
578
+ error: `Exchange data unavailable for ${p.symbol} (Hyperliquid open-orders fetch failed). ` +
579
+ `Skipping audit this cycle — do NOT run attach_brackets or ` +
580
+ `close_position(reason='bracket_integrity') on the basis of this call. Underlying: ${m}`,
581
+ };
582
+ }
583
+ const ledgerRow = ledger.getBySymbol(p.symbol) ?? null;
584
+ const rowNonTerminalWithCids = !!ledgerRow && !isTerminalBracketState(ledgerRow.state) && Boolean(ledgerRow.slCid || ledgerRow.tpCid);
585
+ if (symbolOrders.length === 0 && rowNonTerminalWithCids) {
586
+ // Untrusted-empty contradiction (the SOL/INJ lesson, HL edition): the
587
+ // ledger says legs are live but the successful fetch shows nothing.
588
+ // Empty is NOT positive disconfirmation — refuse honestly.
589
+ return {
590
+ error: `Cannot confirm bracket protection for ${p.symbol} — the exchange returned an empty ` +
591
+ `open-orders set while the ledger row is ${ledgerRow.state} with stored cloids. ` +
592
+ `Empty ≠ "brackets gone". Skipping audit this cycle; do NOT run attach_brackets or ` +
593
+ `close_position(reason='bracket_integrity'). Retry next heartbeat.`,
594
+ };
595
+ }
596
+ let hasBracketStop = false;
597
+ let hasBracketTarget = false;
598
+ let hasManualStop = false;
599
+ let hasManualTarget = false;
600
+ let stopPrice;
601
+ let targetPrice;
602
+ for (const o of symbolOrders) {
603
+ const parsed = o.clientOrderId ? parseHlBracketCloid(o.clientOrderId) : null;
604
+ const triggerPrice = extractTriggerPrice(o);
605
+ const info = o.info ?? {};
606
+ if (parsed?.role === 'stop') {
607
+ hasBracketStop = true;
608
+ stopPrice = triggerPrice ?? stopPrice;
609
+ }
610
+ else if (parsed?.role === 'target') {
611
+ hasBracketTarget = true;
612
+ targetPrice = triggerPrice ?? targetPrice;
613
+ }
614
+ else if (isProtectiveHlOrder(info)) {
615
+ const t = String(info.orderType ?? o.type ?? '').toLowerCase();
616
+ if (t.includes('take profit') || t.includes('tp') || t.includes('take_profit')) {
617
+ hasManualTarget = true;
618
+ targetPrice = triggerPrice ?? targetPrice;
619
+ }
620
+ else {
621
+ hasManualStop = true;
622
+ stopPrice = triggerPrice ?? stopPrice;
623
+ }
624
+ }
625
+ }
626
+ const hasStop = hasBracketStop || hasManualStop;
627
+ const hasTarget = hasBracketTarget || hasManualTarget;
628
+ if (ledgerRow?.stopPrice !== undefined)
629
+ stopPrice = ledgerRow.stopPrice;
630
+ if (ledgerRow?.targetPrice !== undefined)
631
+ targetPrice = ledgerRow.targetPrice;
632
+ let reason;
633
+ if (hasStop || hasTarget) {
634
+ reason = hasBracketStop || hasBracketTarget ? 'bracket_active' : 'manual_order_protecting';
635
+ }
636
+ else if (ledgerRow &&
637
+ (ledgerRow.state === 'active' || ledgerRow.state === 'partial' || ledgerRow.state === 'attaching')) {
638
+ reason = 'stale_ledger_row';
639
+ }
640
+ else if (ledgerRow && isTerminalBracketState(ledgerRow.state)) {
641
+ reason = 'ledger_terminal_no_order';
642
+ }
643
+ else {
644
+ reason = 'no_protective_order';
645
+ }
646
+ // ★ T-2 under-coverage: a stop can exist AND still leave scaled-in size
647
+ // naked (fixed-size legs). Surface it in logs; the adapter's 60s
648
+ // truth-check sweep auto-resizes.
649
+ const contracts = Math.abs(Number(p.contracts ?? 0));
650
+ if (hasBracketStop && ledgerRow?.qty !== undefined && ledgerRow.qty + 1e-8 < contracts) {
651
+ logger.warn(TAG, `HL audit: ${p.symbol} stop leg sized ${ledgerRow.qty} vs position ${contracts} — ` +
652
+ 'under-covered (T-2); the truth-check sweep will resize');
653
+ }
654
+ entries.push({
655
+ symbol: p.symbol,
656
+ side: p.side,
657
+ contracts: p.contracts,
658
+ entry_price: p.entryPrice,
659
+ mark_price: p.markPrice,
660
+ has_stop: hasStop,
661
+ has_target: hasTarget,
662
+ stop_price: stopPrice,
663
+ target_price: targetPrice,
664
+ ledger_state: ledgerRow?.state ?? 'no_ledger_row',
665
+ reason,
666
+ recommended_action: hasStop ? 'none' : 'attach_brackets',
667
+ });
668
+ }
669
+ const nowTs = Date.now();
670
+ for (const e of entries) {
671
+ recordVerdictAndCheckFlicker(normalizeBracketSymbol(e.symbol), e.has_stop, nowTs);
672
+ }
673
+ const unprotected = entries.filter(e => !e.has_stop).length;
674
+ return {
675
+ ok: true,
676
+ mode: 'live',
677
+ brackets_enabled: true,
678
+ total_positions: entries.length,
679
+ protected_count: entries.length - unprotected,
680
+ unprotected_count: unprotected,
681
+ positions: entries,
682
+ };
683
+ }
@@ -0,0 +1,12 @@
1
+ import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { BracketLedger } from '../live/bracket-ledger.js';
3
+ export interface BracketControl {
4
+ venue: 'binance' | 'hyperliquid';
5
+ ledger: BracketLedger;
6
+ modifyStop(symbol: string, newStopPrice: number): Promise<void>;
7
+ modifyTarget(symbol: string, newTargetPrice: number): Promise<void>;
8
+ }
9
+ /** Resolve the venue's bracket-mutation surface, or null when brackets are
10
+ * not enabled (Binance with brackets.mode=off; never null on HL live —
11
+ * exchange-side legs are HL live's ONLY protection, so they are always on). */
12
+ export declare function resolveBracketControl(adapter: IExchangeAdapter): BracketControl | null;
@@ -0,0 +1,35 @@
1
+ // Venue dispatch for the bracket-mutating tools (issue #209).
2
+ //
3
+ // modify_stop / modify_target need one seam: "give me the thing that can move
4
+ // a leg, plus the ledger that knows the current geometry". On Binance that is
5
+ // BracketManager (+ LiveAdapter's ledger); on Hyperliquid it is the
6
+ // HlBracketCoordinator (which owns its own venue-distinct ledger). The tools
7
+ // must never know which venue they are on beyond this resolver — the
8
+ // hard-cast-to-LiveAdapter pattern is exactly what left the HL arm returning
9
+ // "Bracket orders are not enabled" on every call (the #209 recovery gap).
10
+ import { HyperliquidLiveAdapter } from '../venues/hyperliquid/hl-live-adapter.js';
11
+ /** Resolve the venue's bracket-mutation surface, or null when brackets are
12
+ * not enabled (Binance with brackets.mode=off; never null on HL live —
13
+ * exchange-side legs are HL live's ONLY protection, so they are always on). */
14
+ export function resolveBracketControl(adapter) {
15
+ if (adapter instanceof HyperliquidLiveAdapter) {
16
+ const coordinator = adapter.getHlBracketCoordinator();
17
+ return {
18
+ venue: 'hyperliquid',
19
+ ledger: coordinator.getLedger(),
20
+ modifyStop: (symbol, px) => coordinator.modifyStop(symbol, px),
21
+ modifyTarget: (symbol, px) => coordinator.modifyTarget(symbol, px),
22
+ };
23
+ }
24
+ const live = adapter;
25
+ const mgr = live.getBracketManager?.();
26
+ const ledger = live.getBracketLedger?.();
27
+ if (!mgr || !ledger)
28
+ return null;
29
+ return {
30
+ venue: 'binance',
31
+ ledger,
32
+ modifyStop: (symbol, px) => mgr.modifyStop(symbol, px),
33
+ modifyTarget: (symbol, px) => mgr.modifyTarget(symbol, px),
34
+ };
35
+ }
@@ -1,7 +1,9 @@
1
1
  import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
2
3
  import type { CcxtOrder } from '../types.js';
3
4
  export declare function cancelAllOrdersTool(args: {
4
5
  symbol?: string;
5
6
  }, deps: {
6
7
  adapter: IExchangeAdapter;
8
+ operationLock?: TradingOperationLock;
7
9
  }): Promise<CcxtOrder[]>;
@@ -1,5 +1,8 @@
1
1
  // Tool: cancel_all_orders — cancel all open orders (paper or live)
2
2
  // NO readiness gate — emergency control (kill switch), must always work.
3
3
  export async function cancelAllOrdersTool(args, deps) {
4
- return deps.adapter.cancelAllOrders(args.symbol);
4
+ const cancel = () => deps.adapter.cancelAllOrders(args.symbol);
5
+ return deps.adapter.isLive && deps.operationLock
6
+ ? deps.operationLock.withAccountLock(`generic-cancel-all-orders-final:${args.symbol ?? 'all'}`, cancel)
7
+ : cancel();
5
8
  }
@@ -1,10 +1,14 @@
1
1
  import type { IExchangeAdapter } from '../exchange-adapter.js';
2
+ import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
2
3
  import type { CcxtOrder } from '../types.js';
4
+ import type { Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
3
5
  export declare function cancelOrderTool(args: {
4
6
  id: string;
5
7
  symbol?: string;
6
8
  }, deps: {
7
9
  adapter: IExchangeAdapter;
10
+ operationLock?: TradingOperationLock;
11
+ checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
8
12
  }): Promise<CcxtOrder | {
9
13
  error: string;
10
14
  }>;
@@ -1,12 +1,58 @@
1
- // Tool: cancel_order — cancel a single order (paper or live)
2
- // NO readiness gate emergency control, must always work.
1
+ // Tool: cancel_order — cancel a single order (paper or live).
2
+ // There is no readiness gate, but LIVE cancellation cannot individually strip
3
+ // candidate-bound Wave 9 protection. The emergency escape is an explicit
4
+ // operator close, which reduces exposure instead of leaving it naked.
3
5
  import { formatError } from '../logger.js';
4
6
  export async function cancelOrderTool(args, deps) {
5
7
  if (!args.id) {
6
8
  return { error: 'Order ID is required.' };
7
9
  }
10
+ if (!deps.adapter.isLive) {
11
+ try {
12
+ return await deps.adapter.cancelOrder(args.id, args.symbol);
13
+ }
14
+ catch (err) {
15
+ return { error: formatError(err) };
16
+ }
17
+ }
18
+ if (deps.checkWave9LiveSymbolOwnership && !deps.operationLock) {
19
+ return {
20
+ error: 'Live cancel_order refused: the shared trading operation lock is required ' +
21
+ 'while Wave 9 LIVE ownership checks are enabled.',
22
+ };
23
+ }
24
+ const cancelLive = async () => {
25
+ let symbol = args.symbol?.trim();
26
+ if (!symbol) {
27
+ const matches = (await deps.adapter.getOpenOrders())
28
+ .filter((order) => order.id === args.id);
29
+ if (matches.length !== 1 || !matches[0].symbol) {
30
+ throw new Error(`cancel_order refused: order ${args.id} could not be resolved to exactly one ` +
31
+ `trusted open-order symbol (matches=${matches.length}).`);
32
+ }
33
+ symbol = matches[0].symbol;
34
+ }
35
+ if (deps.checkWave9LiveSymbolOwnership) {
36
+ let ownership;
37
+ try {
38
+ ownership = await deps.checkWave9LiveSymbolOwnership(symbol);
39
+ }
40
+ catch (err) {
41
+ throw new Error(`cancel_order refused at mutation boundary: Wave 9 LIVE ownership check failed ` +
42
+ `for ${symbol} (${formatError(err)}).`);
43
+ }
44
+ if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
45
+ throw new Error(`cancel_order refused at mutation boundary: ${symbol} has ` +
46
+ `${ownership.status} Wave 9 LIVE ownership (${ownership.reason}). ` +
47
+ 'Use operator_command to close the position instead of stripping candidate-bound protection.');
48
+ }
49
+ }
50
+ return deps.adapter.cancelOrder(args.id, symbol);
51
+ };
8
52
  try {
9
- return await deps.adapter.cancelOrder(args.id, args.symbol);
53
+ return deps.operationLock
54
+ ? await deps.operationLock.withAccountLock(`generic-cancel-order-final:${args.id}`, cancelLive)
55
+ : await cancelLive();
10
56
  }
11
57
  catch (err) {
12
58
  return { error: formatError(err) };
@@ -4,6 +4,11 @@ import type { CcxtOrder } from '../types.js';
4
4
  import { type ClosePositionArgs } from './assessment-validation.js';
5
5
  import { type AutoCaptureContext } from '../ingest/position-auto-capture.js';
6
6
  import { type ExitGateMode } from '../config/position-review-config.js';
7
+ import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
8
+ import type { Wave9LiveExecutionLedger } from '../wave9/live-execution-ledger.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';
7
12
  export declare function closePositionTool(args: ClosePositionArgs, deps: {
8
13
  binanceApi: PublicMarketDataApi;
9
14
  adapter: IExchangeAdapter;
@@ -11,6 +16,24 @@ export declare function closePositionTool(args: ClosePositionArgs, deps: {
11
16
  /** Override for tests — production reads from plugin-config.json on each
12
17
  * call so the flag can be flipped without a restart. */
13
18
  exitGateMode?: ExitGateMode;
19
+ wave9AdmissionGuard?: Wave9PaperAdmissionGuard;
20
+ refreshPaperState?: () => void;
21
+ wave9TradingModeCheck?: () => string;
22
+ wave9VenueCheck?: () => string;
23
+ captureWave9PositionFingerprint?: (symbol: string) => Promise<{
24
+ fingerprint: string;
25
+ position: {
26
+ candidateId?: string;
27
+ missionId?: string;
28
+ setupType?: string;
29
+ side: 'long' | 'short';
30
+ };
31
+ }>;
32
+ wave9LiveLedger?: Wave9LiveExecutionLedger;
33
+ checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
34
+ reprotectWave9LiveResidual?: (adapter: IExchangeAdapter, ledger: Wave9LiveExecutionLedger, candidateId: string, symbol: string) => Promise<Wave9LiveResidualProtectionResult>;
35
+ operationLock?: TradingOperationLock;
36
+ operationLockHeld?: boolean;
14
37
  }): Promise<CcxtOrder | {
15
38
  error: string;
16
39
  }>;