@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
@@ -43,17 +43,10 @@ const TAG = 'reconcile-db-vs-exchange';
43
43
  function canonical(symbol) {
44
44
  return symbol.split(':')[0];
45
45
  }
46
- /**
47
- * Close webapp `positions` rows that are status='open' but absent from the
48
- * (trusted) exchange snapshot. Returns the number of synthetic closes posted.
49
- *
50
- * @param exchangeSymbols symbols from a TRUSTED snapshot (getPositionsOrNull()
51
- * !== null). Pass only when the fetch genuinely succeeded.
52
- */
53
- export async function reconcileDbOpenVsExchange(ctx, exchangeSymbols, nowMs = Date.now()) {
46
+ export async function reconcileDbOpenVsExchange(ctx, exchangeSymbols, nowMs = Date.now(), opts = {}) {
54
47
  if (!ctx.decisionsClient || !ctx.userId)
55
48
  return 0;
56
- const resp = await ctx.decisionsClient.getOpenPositions(ctx.userId);
49
+ const resp = await ctx.decisionsClient.getOpenPositions(ctx.userId, ctx.resolveMode?.(), ctx.resolveExchange?.());
57
50
  if (!resp) {
58
51
  // null = fetch failed (network / terminal). NEVER treat as "no open rows"
59
52
  // (null≠empty) — skip; the next boot / periodic sweep retries.
@@ -77,12 +70,17 @@ export async function reconcileDbOpenVsExchange(ctx, exchangeSymbols, nowMs = Da
77
70
  logger.warn(TAG, `${o.symbol}: bad size/price from DB (size=${o.remainingSize}, entry=${o.entryPrice}) — skipping close`);
78
71
  continue;
79
72
  }
73
+ const lastExitInfo = opts.describeLastExit?.(o.symbol);
74
+ if (lastExitInfo) {
75
+ logger.warn(TAG, `${o.symbol}: capture-miss attribution — ${lastExitInfo}`);
76
+ }
80
77
  const close = {
81
78
  positionId: o.id,
82
79
  closeAt: nowMs,
83
80
  closeReason: 'reconciler_observed_flat',
84
81
  closeAssessment: {
85
- source: 'db_exchange_sweep_boot',
82
+ source: opts.source ?? 'db_exchange_sweep_boot',
83
+ ...(lastExitInfo ? { last_engine_trade: lastExitInfo } : {}),
86
84
  synthetic: true,
87
85
  note: 'Position open in DB but absent from the exchange snapshot. It was ' +
88
86
  'closed on-exchange via a path that bypassed close_position (bracket ' +
@@ -112,3 +110,61 @@ export async function reconcileDbOpenVsExchange(ctx, exchangeSymbols, nowMs = Da
112
110
  }
113
111
  return posted;
114
112
  }
113
+ // ---- Periodic sweep (issues #199/#203) ----
114
+ //
115
+ // The boot-only sweep left phantoms alive for DAYS between restarts (paper:
116
+ // the LTC phantom of issue #199 lived 26.5h; live: 38 of 85 closes in 14 days
117
+ // were harvested only at the daily restart, issue #203). This interval runs
118
+ // the same DB-authoritative pass continuously with the same trusted-snapshot
119
+ // discipline: getPositionsOrNull() null → skip, never treat as flat.
120
+ export const DEFAULT_DB_RECONCILE_INTERVAL_MS = 300_000;
121
+ const MIN_DB_RECONCILE_INTERVAL_MS = 60_000;
122
+ /** Resolve the sweep interval from RC_DB_RECONCILE_INTERVAL_MS.
123
+ * 'off' or '0' disables (returns null); values are clamped to ≥60s. */
124
+ export function resolveDbReconcileIntervalMs(raw = process.env.RC_DB_RECONCILE_INTERVAL_MS) {
125
+ if (raw === 'off' || raw === '0')
126
+ return null;
127
+ const n = Number(raw);
128
+ if (!Number.isFinite(n) || n <= 0)
129
+ return DEFAULT_DB_RECONCILE_INTERVAL_MS;
130
+ return Math.max(MIN_DB_RECONCILE_INTERVAL_MS, n);
131
+ }
132
+ export function startPeriodicDbReconcile(deps, intervalMs = resolveDbReconcileIntervalMs()) {
133
+ if (intervalMs === null) {
134
+ logger.info(TAG, 'periodic sweep disabled (RC_DB_RECONCILE_INTERVAL_MS=off)');
135
+ return null;
136
+ }
137
+ let running = false;
138
+ const runOnce = async () => {
139
+ if (running)
140
+ return 0; // concurrency guard — a slow sweep never overlaps
141
+ running = true;
142
+ try {
143
+ const adapter = deps.resolveAdapter();
144
+ if (!adapter)
145
+ return 0;
146
+ const positions = await adapter.getPositionsOrNull();
147
+ if (positions === null) {
148
+ // Untrusted snapshot — NEVER read as flat (null≠empty).
149
+ logger.warn(TAG, 'periodic sweep skipped — positions fetch untrusted (null)');
150
+ return 0;
151
+ }
152
+ return await reconcileDbOpenVsExchange(deps, positions.map((p) => p.symbol), Date.now(), { source: 'db_exchange_sweep_periodic', describeLastExit: deps.describeLastExit });
153
+ }
154
+ catch (err) {
155
+ logger.warn(TAG, `periodic sweep failed: ${err instanceof Error ? err.message : String(err)}`);
156
+ return 0;
157
+ }
158
+ finally {
159
+ running = false;
160
+ }
161
+ };
162
+ const timer = setInterval(() => { void runOnce(); }, intervalMs);
163
+ // Never keep the process alive just for the sweep.
164
+ timer.unref?.();
165
+ logger.info(TAG, `periodic DB-vs-exchange sweep started (interval ${intervalMs}ms)`);
166
+ return {
167
+ stop: () => clearInterval(timer),
168
+ runOnce,
169
+ };
170
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Narrow process-local serialization boundary shared by guarded trading
3
+ * operations. Account and per-symbol operations intentionally use one queue:
4
+ * a Wave 9 portfolio transition must be atomic with respect to the legacy
5
+ * stop watcher, while callers gain no adapter/execution capability from the
6
+ * lock itself.
7
+ */
8
+ export interface TradingOperationLock {
9
+ withAccountLock<T>(operation: string, run: () => Promise<T>): Promise<T>;
10
+ withSymbolLock<T>(symbol: string, operation: string, run: () => Promise<T>): Promise<T>;
11
+ }
12
+ export declare class SerialTradingOperationLock implements TradingOperationLock {
13
+ private tail;
14
+ private enqueue;
15
+ withAccountLock<T>(_operation: string, run: () => Promise<T>): Promise<T>;
16
+ withSymbolLock<T>(_symbol: string, _operation: string, run: () => Promise<T>): Promise<T>;
17
+ }
@@ -0,0 +1,14 @@
1
+ export class SerialTradingOperationLock {
2
+ tail = Promise.resolve();
3
+ enqueue(run) {
4
+ const result = this.tail.then(run, run);
5
+ this.tail = result.then(() => undefined, () => undefined);
6
+ return result;
7
+ }
8
+ withAccountLock(_operation, run) {
9
+ return this.enqueue(run);
10
+ }
11
+ withSymbolLock(_symbol, _operation, run) {
12
+ return this.enqueue(run);
13
+ }
14
+ }
@@ -1,5 +1,5 @@
1
- import type { VenueId } from '@reefclaw/shared';
2
1
  import type { BracketId, BracketRole } from './bracket-types.js';
2
+ import type { VenueId } from '../venues/symbols.js';
3
3
  /** Generate a fresh 16-char hex bracketId. */
4
4
  export declare function generateBracketId(): BracketId;
5
5
  /** Build a clientOrderId for a given bracket + role. Emits the CURRENT
@@ -17,8 +17,7 @@ export declare function parseBracketCid(cid: string): {
17
17
  /** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
18
18
  * — true for both the current and legacy schemes. */
19
19
  export declare function isBracketCid(cid: string): boolean;
20
- /** Venue-aware parseBracketCid. Binance delegates to the existing dual-scheme
21
- * parser; other venues return null until their scheme ships. */
20
+ /** Venue-aware parseBracketCid Binance's dual scheme, Hyperliquid's cloids. */
22
21
  export declare function parseBracketClientId(venue: VenueId, cid: string): {
23
22
  bracketId: BracketId;
24
23
  role: BracketRole;
@@ -35,6 +35,7 @@
35
35
  // alphanumeric) is well within it. Verified vs developers.binance.com
36
36
  // USDⓂ New-Order clientOrderId filter, 2026-05-16.
37
37
  import { randomBytes } from 'node:crypto';
38
+ import { parseHlBracketCloid, isHlBracketCloid } from '../venues/hyperliquid/hl-cloid.js';
38
39
  // Emitted scheme (no `rc-`, no dashes).
39
40
  const CID_REGEX = /^bkt([0-9a-f]{16})([est])$/;
40
41
  // Legacy scheme — still parsed for backward compat, never emitted again.
@@ -83,17 +84,29 @@ export function isBracketCid(cid) {
83
84
  //
84
85
  // ALL bracket client-order-id recognition stays centralized in THIS module —
85
86
  // the brackets.md rule ("never hardcode an rc-/bkt regex elsewhere") extends
86
- // per-venue. Binance = the bkt/rc- schemes above. Hyperliquid client order
87
- // ids are 128-bit hex cloids with a different scheme (hl-cloid.ts, Phase 3 of
88
- // docs/HYPERLIQUID_INTEGRATION_PLAN.md §5.7) and will be dispatched from
89
- // here; until then the hyperliquid arm recognises nothing, which is correct —
90
- // this build never emits an HL bracket order.
91
- /** Venue-aware parseBracketCid. Binance delegates to the existing dual-scheme
92
- * parser; other venues return null until their scheme ships. */
87
+ // per-venue. Binance = the bkt/rc- schemes above. Hyperliquid = 128-bit hex
88
+ // cloids (`0xbc7…`, hl-cloid.ts §5.7 a Binance-style ASCII cid is not even a
89
+ // legal HL client id). Phase 3 wires that dispatch HERE, and nowhere else.
90
+ //
91
+ // NEITHER VENUE MAY EVER RECOGNISE THE OTHER'S SCHEME. A false positive is
92
+ // destructive: `cancelAllOrders` preserves anything that parses as "ours", and
93
+ // the reconciler cancels/re-attaches on that basis. A foreign id that parsed as
94
+ // ours would be treated as a protective leg we own.
95
+ /** Venue-aware parseBracketCid — Binance's dual scheme, Hyperliquid's cloids. */
93
96
  export function parseBracketClientId(venue, cid) {
94
- return venue === 'binance' ? parseBracketCid(cid) : null;
97
+ switch (venue) {
98
+ case 'binance':
99
+ return parseBracketCid(cid);
100
+ case 'hyperliquid':
101
+ return parseHlBracketCloid(cid);
102
+ }
95
103
  }
96
104
  /** Venue-aware isBracketCid. */
97
105
  export function isBracketClientId(venue, cid) {
98
- return venue === 'binance' ? isBracketCid(cid) : false;
106
+ switch (venue) {
107
+ case 'binance':
108
+ return isBracketCid(cid);
109
+ case 'hyperliquid':
110
+ return isHlBracketCloid(cid);
111
+ }
99
112
  }
@@ -0,0 +1,13 @@
1
+ /** Minimal order shape needed to resolve a fill price. */
2
+ export interface FillPriceOrder {
3
+ average: number | null;
4
+ filled: number;
5
+ cost: number;
6
+ /** Present only to make explicit that we deliberately DO NOT read it. */
7
+ price?: number | null;
8
+ }
9
+ /**
10
+ * The average price a filled order actually executed at, or null if it can't
11
+ * be determined from the response. Never returns the limit price.
12
+ */
13
+ export declare function fillPriceFromOrder(order: FillPriceOrder): number | null;
@@ -0,0 +1,37 @@
1
+ // Resolve the ACTUAL average fill price of a (partially) filled order.
2
+ //
3
+ // Issue #196 (prod LTC/USDT 2026-07-14, TAO/USDT 2026-07-15): for marketable
4
+ // LIMIT orders, Binance's synchronous USD-M order response
5
+ // (newOrderRespType=RESULT) can carry avgPrice=0 even though executedQty>0 —
6
+ // per the Binance doc, RESULT only guarantees the final fill for MARKET orders
7
+ // and LIMIT orders with a special timeInForce, NOT a plain GTC LIMIT. Code that
8
+ // then used `order.price` (the LIMIT price) as the entry proxy validated bracket
9
+ // geometry / journaled the entry against a price the order never filled at —
10
+ // several percent off for a marketable limit that crossed. That produced:
11
+ // - false "bracket direction invalid" auto-flattens (LTC: SELL limit 43 filled
12
+ // 44.49, target 43.21 compared vs 43 → "must be BELOW 43" → needless scratch), and
13
+ // - wrong journal entry prices (TAO: BUY limit 202 filled ~196.23 → journaled 202).
14
+ //
15
+ // The LIMIT price is NEVER a valid substitute for the fill. This resolver
16
+ // returns the true average fill from the response alone (no network), else null;
17
+ // callers must re-query or fall back to a mark price, never to `order.price`.
18
+ /**
19
+ * The average price a filled order actually executed at, or null if it can't
20
+ * be determined from the response. Never returns the limit price.
21
+ */
22
+ export function fillPriceFromOrder(order) {
23
+ // 1. The exchange-reported average fill price, when present and positive.
24
+ if (typeof order.average === 'number' && Number.isFinite(order.average) && order.average > 0) {
25
+ return order.average;
26
+ }
27
+ // 2. VWAP from cost/filled. For USD-M futures ccxt maps cost = cumQuote =
28
+ // Σ(price×qty), so cost/filled = Σ(price×qty)/Σqty = the exact avgPrice.
29
+ // Covers the case where avgPrice=0 in the response but cumQuote settled.
30
+ if (typeof order.cost === 'number' && Number.isFinite(order.cost) && order.cost > 0 &&
31
+ typeof order.filled === 'number' && Number.isFinite(order.filled) && order.filled > 0) {
32
+ return order.cost / order.filled;
33
+ }
34
+ // 3. Unknown. The caller MUST NOT fall back to order.price (the limit) — that
35
+ // is exactly the marketable-limit bug this helper exists to prevent.
36
+ return null;
37
+ }
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import type { IExchangeAdapter, AdapterReadiness, OrderOptions } from '../exchange-adapter.js';
2
+ import type { IExchangeAdapter, AdapterReadiness, ClientOrderResolution, OrderOptions } from '../exchange-adapter.js';
3
3
  import { BinancePrivateApi } from '../ccxt/binance-private.js';
4
+ import type { BinanceAlgoOrderProof } from '../ccxt/binance-private.js';
4
5
  import type { CcxtOrder, CcxtBalance, CcxtPosition, TradingMode, ExchangeConfig } from '../types.js';
5
6
  import type { PositionMetadata, CloseReason } from '../simulator/types.js';
6
7
  import { ExchangeInfoCache } from './exchange-info-cache.js';
@@ -16,7 +17,7 @@ import { BracketManager } from './bracket-manager.js';
16
17
  import { type BracketMode } from '../config/brackets-config.js';
17
18
  import { type UserDataStreamMode, type UserDataStreamTunables } from '../config/user-data-stream-config.js';
18
19
  import { UserDataStreamController } from './user-data-stream-controller.js';
19
- import type { AutoCaptureContext } from '../ingest/position-auto-capture.js';
20
+ import { type AutoCaptureContext } from '../ingest/position-auto-capture.js';
20
21
  import type { TradeStoreClient } from '../ingest/trade-store-client.js';
21
22
  /** Optional audit-trail wiring (TRADE_AUDIT_TRAIL_PLAN Phase 1). Caller
22
23
  * passes this only when userDataStream.dbWrite='on' AND the WEBAPP_INGEST_TOKEN
@@ -27,6 +28,16 @@ export interface TradeIngestWiring {
27
28
  userId: string;
28
29
  exchange?: string;
29
30
  }
31
+ export interface Wave9ProtectionLossEvent {
32
+ source: 'algo_canceled' | 'algo_rejected' | 'reconciler_missing';
33
+ symbol: string;
34
+ bracketId: string;
35
+ entryClientOrderId: string;
36
+ entrySide: 'buy' | 'sell';
37
+ stopClientOrderId?: string;
38
+ detail: string;
39
+ }
40
+ export type Wave9ProtectionLossHandler = (event: Wave9ProtectionLossEvent) => Promise<void> | void;
30
41
  /**
31
42
  * Backfill `balance.free[cur] = total[cur]` for currencies where Binance's CCXT
32
43
  * response left the per-currency `free` and `used` at zero despite a non-zero
@@ -55,6 +66,10 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
55
66
  private bracketLedger;
56
67
  private bracketManager;
57
68
  private bracketReconciler;
69
+ private wave9ProtectionLossHandler?;
70
+ private readonly wave9ProtectionLossInFlight;
71
+ private readonly pendingWave9ProtectionLossBySymbol;
72
+ private static readonly PENDING_WAVE9_PROTECTION_LOSS_CAP;
58
73
  private readonly userDataStreamMode;
59
74
  private userDataStream;
60
75
  private readonly wsAlgoStatusByCid;
@@ -64,6 +79,7 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
64
79
  private lastIncomeRefreshMs;
65
80
  private incomeAnchorUtcDay;
66
81
  private liveMetadata;
82
+ private autoCapture?;
67
83
  private _readiness;
68
84
  get readiness(): AdapterReadiness;
69
85
  /** Session-start NAV, captured once at initialization. Used for drawdown calculation. */
@@ -128,6 +144,32 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
128
144
  * the subsequent flatten.
129
145
  */
130
146
  private attachBracketsAsync;
147
+ /**
148
+ * Patch `order.average` with the authoritative average fill price when a
149
+ * filled order came back without one (marketable-limit RESULT quirk, #196).
150
+ * Re-queries by clientOrderId (GET /fapi/v1/order, weight 1). Best-effort:
151
+ * on any failure or an unresolvable re-query the order is left as-is —
152
+ * downstream then falls back to a mark price / skips capture, NEVER to the
153
+ * limit price. Mutates `order` in place.
154
+ */
155
+ private enrichFilledAvgPrice;
156
+ /**
157
+ * Journal the close produced by an internal auto-flatten
158
+ * (`bracket_attach_failed`), so the position row can't orphan as
159
+ * status='open' (issue #196). These flattens go straight to
160
+ * `this.closePosition`, bypassing the close_position tool, so
161
+ * `onClosePositionFilled` never runs for them. Fire-and-forget + fail-open —
162
+ * never blocks or throws into the flatten path. The entry journal may not be
163
+ * ready at flatten time (the entry's WS fill can land ~1-2s later), so
164
+ * `onAutoFlattenClose` polls briefly for the webappPositionId; the boot DB
165
+ * reconcile sweep is the backstop if the entry never journals.
166
+ */
167
+ private captureAutoFlattenClose;
168
+ /** Install the Wave 9-only autonomous recovery bridge. With no handler,
169
+ * every generic bracket lifecycle remains byte-for-byte behaviorally
170
+ * unchanged. Runtime reapplies this setter after adapter reconnects. */
171
+ setWave9ProtectionLossHandler(handler?: Wave9ProtectionLossHandler): void;
172
+ private notifyWave9ProtectionLoss;
131
173
  /**
132
174
  * AUTHORITATIVE bracket-lifecycle handler — Binance's ALGO_UPDATE push.
133
175
  *
@@ -194,6 +236,8 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
194
236
  * fired (TRIGGERED/FINISHED) — only this licenses repair.
195
237
  * 'unknown' = could not get an answer — safe-wait. */
196
238
  resolveBracketLegLiveness(cid: string): Promise<'live' | 'terminal' | 'unknown'>;
239
+ /** Immediate full-shape proof for one exact native conditional order. */
240
+ resolveBracketLegProof(cid: string): Promise<BinanceAlgoOrderProof | null>;
197
241
  /** Symbol-scoped generalization of the trusted-WS fast-path — the
198
242
  * authoritative action-layer guard for the 2026-05-16 INJ bracket spiral.
199
243
  *
@@ -237,6 +281,16 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
237
281
  * drift shape (re-emitted upstream as `bracket_drift`) plus a loud log so
238
282
  * the operator knows re-protection is required. */
239
283
  private reattachBracketsAfterFailedClose;
284
+ /** Null-honest balance read: null = the fetch FAILED (429 / weight-paced /
285
+ * banned) — the caller MUST treat it as UNKNOWN, never as a zero balance.
286
+ * Same null≠empty contract as getPositionsOrNull. Decision paths (sizing,
287
+ * pre-trade risk) must use this: the legacy getBalance() collapse below
288
+ * reads as walletTotal=0 → ~−100% drawdown → RED zone. */
289
+ getBalanceOrNull(): Promise<CcxtBalance | null>;
290
+ /** Legacy display-path read. Collapses a FAILED fetch to an empty balance
291
+ * object — acceptable for read-only surfaces (fetch_balance tool,
292
+ * risk-summary display), a phantom-zero hazard for anything that decides.
293
+ * Decision paths use getBalanceOrNull(). */
240
294
  getBalance(): Promise<CcxtBalance>;
241
295
  /** Re-anchor the realized-today seed + sessionStartNav from /fapi/v1/income.
242
296
  * Throttled to {@link INCOME_REFRESH_MIN_INTERVAL_MS}; UTC date rollover
@@ -256,6 +310,7 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
256
310
  private decoratePositionsWithMetadata;
257
311
  getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
258
312
  fetchOrder(orderId: string, symbol?: string): Promise<CcxtOrder | null>;
313
+ resolveOrderByClientId(clientOrderId: string, symbol: string): Promise<ClientOrderResolution>;
259
314
  getLastPrice(symbol: string): Promise<number | null>;
260
315
  /** Sync rate limiter from exchange response headers after every private API call. */
261
316
  private syncRateLimits;