@reefclaw/openclaw-plugin 0.1.4 → 0.1.6

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 (124) hide show
  1. package/ccxt/binance-private.d.ts +21 -0
  2. package/ccxt/binance-private.js +132 -22
  3. package/config/plugin-config-io.d.ts +6 -0
  4. package/config/tool-gate.js +3 -0
  5. package/exchange-adapter.d.ts +16 -0
  6. package/index.js +567 -57
  7. package/lifecycle/trading-operation-lock.d.ts +17 -0
  8. package/lifecycle/trading-operation-lock.js +14 -0
  9. package/live/bracket-id.d.ts +2 -3
  10. package/live/bracket-id.js +22 -9
  11. package/live/live-adapter.d.ts +24 -1
  12. package/live/live-adapter.js +114 -2
  13. package/live/local-signal-service.js +11 -6
  14. package/live/local-strategy-evaluator.js +4 -0
  15. package/live/proposal-decision-listener.d.ts +6 -0
  16. package/live/proposal-decision-listener.js +4 -0
  17. package/live/stop-watcher.d.ts +27 -1
  18. package/live/stop-watcher.js +59 -2
  19. package/onboarding/runtime.d.ts +13 -0
  20. package/onboarding/runtime.js +22 -2
  21. package/openclaw.plugin.json +1 -0
  22. package/package.json +1 -1
  23. package/persistence/state-manager.d.ts +24 -0
  24. package/persistence/state-manager.js +62 -4
  25. package/portfolio/wave9-admission.d.ts +67 -0
  26. package/portfolio/wave9-admission.js +262 -0
  27. package/portfolio/wave9-policy.d.ts +36 -0
  28. package/portfolio/wave9-policy.js +183 -0
  29. package/signals/conditions/registry.js +50 -0
  30. package/simulator/exchange-simulator.js +5 -0
  31. package/simulator/fill-engine.js +5 -1
  32. package/simulator/types.d.ts +6 -1
  33. package/strategy/evaluator.d.ts +3 -0
  34. package/strategy/evaluator.js +5 -0
  35. package/tools/assessment-validation.d.ts +2 -0
  36. package/tools/attach-brackets.d.ts +7 -2
  37. package/tools/attach-brackets.js +36 -0
  38. package/tools/cancel-all-orders.d.ts +2 -0
  39. package/tools/cancel-all-orders.js +4 -1
  40. package/tools/cancel-order.d.ts +4 -0
  41. package/tools/cancel-order.js +49 -3
  42. package/tools/close-position.d.ts +23 -0
  43. package/tools/close-position.js +286 -13
  44. package/tools/create-order.d.ts +28 -0
  45. package/tools/create-order.js +1364 -192
  46. package/tools/get-analytics.js +2 -2
  47. package/tools/get-basis.js +2 -2
  48. package/tools/get-cascade-risk.js +2 -2
  49. package/tools/get-crypto-metrics.js +20 -3
  50. package/tools/get-cvd.js +2 -2
  51. package/tools/get-divergences.js +2 -2
  52. package/tools/get-funding-context.js +2 -2
  53. package/tools/get-liquidation-levels.js +2 -2
  54. package/tools/get-liquidation-pulse.js +2 -2
  55. package/tools/get-pattern-scan.js +2 -2
  56. package/tools/get-regime.js +2 -2
  57. package/tools/get-resting-liquidity.js +2 -2
  58. package/tools/get-risk-scenario.js +2 -2
  59. package/tools/get-session-review.js +2 -2
  60. package/tools/get-setup-detail.js +9 -1
  61. package/tools/get-signals.js +2 -2
  62. package/tools/get-sizing.js +2 -2
  63. package/tools/get-trade-feedback.js +2 -2
  64. package/tools/get-trade-flow.js +2 -2
  65. package/tools/get-volume-profile.js +2 -2
  66. package/tools/get-wave9-status.d.ts +127 -0
  67. package/tools/get-wave9-status.js +796 -0
  68. package/tools/intel-api.d.ts +20 -0
  69. package/tools/intel-api.js +67 -0
  70. package/tools/intel-cache.d.ts +1 -1
  71. package/tools/intel-cache.js +20 -5
  72. package/tools/list-strategies.d.ts +11 -1
  73. package/tools/list-strategies.js +17 -0
  74. package/tools/modify-stop.d.ts +4 -0
  75. package/tools/modify-stop.js +63 -24
  76. package/tools/modify-target.d.ts +4 -0
  77. package/tools/modify-target.js +62 -23
  78. package/tools/scan-pairs.js +19 -8
  79. package/tools/toggle-strategy.js +7 -0
  80. package/types.d.ts +5 -0
  81. package/venues/hyperliquid/hl-balance.d.ts +116 -0
  82. package/venues/hyperliquid/hl-balance.js +145 -0
  83. package/venues/hyperliquid/hl-brackets.d.ts +102 -0
  84. package/venues/hyperliquid/hl-brackets.js +172 -0
  85. package/venues/hyperliquid/hl-cloid.d.ts +22 -0
  86. package/venues/hyperliquid/hl-cloid.js +82 -0
  87. package/venues/hyperliquid/hl-info-cache.d.ts +46 -0
  88. package/venues/hyperliquid/hl-info-cache.js +125 -0
  89. package/venues/hyperliquid/hl-live-adapter.d.ts +88 -0
  90. package/venues/hyperliquid/hl-live-adapter.js +353 -0
  91. package/venues/hyperliquid/hl-precision.d.ts +61 -0
  92. package/venues/hyperliquid/hl-precision.js +176 -0
  93. package/venues/hyperliquid/hl-private.d.ts +88 -0
  94. package/venues/hyperliquid/hl-private.js +357 -0
  95. package/venues/hyperliquid/hl-public.d.ts +31 -4
  96. package/venues/hyperliquid/hl-public.js +155 -11
  97. package/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
  98. package/venues/hyperliquid/hl-rate-gate.js +220 -0
  99. package/venues/hyperliquid/hl-user-stream.d.ts +90 -0
  100. package/venues/hyperliquid/hl-user-stream.js +220 -0
  101. package/venues/registry.d.ts +28 -9
  102. package/venues/registry.js +19 -13
  103. package/venues/symbols.d.ts +43 -0
  104. package/venues/symbols.js +107 -0
  105. package/wave9/live-account-capture.d.ts +67 -0
  106. package/wave9/live-account-capture.js +435 -0
  107. package/wave9/live-autonomous-protection.d.ts +39 -0
  108. package/wave9/live-autonomous-protection.js +112 -0
  109. package/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
  110. package/wave9/live-durable-reconciliation-scheduler.js +115 -0
  111. package/wave9/live-execution-ledger.d.ts +107 -0
  112. package/wave9/live-execution-ledger.js +498 -0
  113. package/wave9/live-position-confirmation.d.ts +18 -0
  114. package/wave9/live-position-confirmation.js +111 -0
  115. package/wave9/live-residual-protection.d.ts +18 -0
  116. package/wave9/live-residual-protection.js +250 -0
  117. package/wave9/live-startup-reconciliation.d.ts +38 -0
  118. package/wave9/live-startup-reconciliation.js +454 -0
  119. package/wave9/live-symbol-ownership.d.ts +20 -0
  120. package/wave9/live-symbol-ownership.js +132 -0
  121. package/wave9/paper-admission-guard.d.ts +199 -0
  122. package/wave9/paper-admission-guard.js +650 -0
  123. package/wave9/usdm-evidence-provider.d.ts +42 -0
  124. package/wave9/usdm-evidence-provider.js +133 -0
@@ -1,4 +1,5 @@
1
1
  import type { CcxtOrder, CcxtBalance, CcxtPosition, CcxtTicker, ExchangeConfig } from '../types.js';
2
+ import type { ClientOrderResolution } from '../exchange-adapter.js';
2
3
  import type { OrderBookDepth } from '../simulator/types.js';
3
4
  /** Result of API permission validation. */
4
5
  export interface PermissionCheckResult {
@@ -7,6 +8,18 @@ export interface PermissionCheckResult {
7
8
  canTrade: boolean;
8
9
  errors: string[];
9
10
  }
11
+ /** Exact, weight-1 proof returned by Binance's per-clientAlgoId endpoint. */
12
+ export interface BinanceAlgoOrderProof {
13
+ algoId: string | null;
14
+ clientAlgoId: string;
15
+ algoStatus: string;
16
+ symbol: string | null;
17
+ side: 'buy' | 'sell' | null;
18
+ orderType: string | null;
19
+ triggerPrice: number | null;
20
+ quantity: number | null;
21
+ closePosition: boolean | null;
22
+ }
10
23
  export declare class BinancePrivateApi {
11
24
  private exchange;
12
25
  private testnet;
@@ -154,6 +167,12 @@ export declare class BinancePrivateApi {
154
167
  * because "I was rate-limited" is just another flavour of "no answer").
155
168
  * Ships dark in Phase 1 — no call sites yet. */
156
169
  queryAlgoOrderStatus(clientAlgoId: string): Promise<string | null>;
170
+ /**
171
+ * Fetch the full exchange shape for one exact conditional order. Unlike the
172
+ * broad open-algo snapshot, this endpoint is immediately usable after
173
+ * placement and is keyed by the stop's unique clientAlgoId.
174
+ */
175
+ queryAlgoOrderProof(clientAlgoId: string): Promise<BinanceAlgoOrderProof | null>;
157
176
  /** Fetch historical fills (trades) for a symbol via GET /fapi/v1/userTrades.
158
177
  * Used by the REST gap-filler (rest-gap-filler.ts) to backfill any fills
159
178
  * that landed during a WS blind window — when the user-data stream was
@@ -214,6 +233,8 @@ export declare class BinancePrivateApi {
214
233
  * after network timeouts — check if the order went through before retrying.
215
234
  */
216
235
  fetchOrderByClientId(clientOrderId: string, symbol: string): Promise<CcxtOrder | null>;
236
+ /** Resolve a deterministic client id without treating read failures as absence. */
237
+ resolveOrderByClientId(clientOrderId: string, symbol: string): Promise<ClientOrderResolution>;
217
238
  /** Cancel a real order on the exchange. Binance Futures splits orders across
218
239
  * two endpoints — regular (`/fapi/v1/order`) and algo/conditional
219
240
  * (`/fapi/v1/algoOrder`). Bracket SL/TP live in the algo bucket.
@@ -682,6 +682,68 @@ export class BinancePrivateApi {
682
682
  return null;
683
683
  }
684
684
  }
685
+ /**
686
+ * Fetch the full exchange shape for one exact conditional order. Unlike the
687
+ * broad open-algo snapshot, this endpoint is immediately usable after
688
+ * placement and is keyed by the stop's unique clientAlgoId.
689
+ */
690
+ async queryAlgoOrderProof(clientAlgoId) {
691
+ if (!clientAlgoId)
692
+ return null;
693
+ try {
694
+ assertNotBanned('queryAlgoOrder');
695
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
696
+ const ex = this.exchange;
697
+ if (typeof ex.fapiPrivateGetAlgoOrder !== 'function')
698
+ return null;
699
+ const raw = await ex.fapiPrivateGetAlgoOrder({ clientAlgoId });
700
+ noteSuccess();
701
+ let row;
702
+ if (Array.isArray(raw)) {
703
+ row = raw
704
+ .find((item) => String(item.clientAlgoId ?? '') === clientAlgoId);
705
+ }
706
+ else if (raw && typeof raw === 'object') {
707
+ const candidate = raw;
708
+ if (String(candidate.clientAlgoId ?? '') === clientAlgoId)
709
+ row = candidate;
710
+ }
711
+ if (!row)
712
+ return null;
713
+ const algoStatus = String(row.algoStatus ?? '').trim().toUpperCase();
714
+ if (!algoStatus)
715
+ return null;
716
+ const sideValue = String(row.side ?? '').trim().toLowerCase();
717
+ const side = sideValue === 'buy' || sideValue === 'sell' ? sideValue : null;
718
+ const triggerPriceValue = Number(row.triggerPrice);
719
+ const quantityValue = Number(row.quantity);
720
+ const closePositionValue = row.closePosition;
721
+ const closePosition = closePositionValue === true
722
+ || String(closePositionValue).toLowerCase() === 'true'
723
+ ? true
724
+ : closePositionValue === false || String(closePositionValue).toLowerCase() === 'false'
725
+ ? false
726
+ : null;
727
+ return {
728
+ algoId: row.algoId == null ? null : String(row.algoId),
729
+ clientAlgoId,
730
+ algoStatus,
731
+ symbol: row.symbol == null ? null : String(row.symbol),
732
+ side,
733
+ orderType: row.orderType == null ? null : String(row.orderType).toUpperCase(),
734
+ triggerPrice: Number.isFinite(triggerPriceValue) && triggerPriceValue > 0
735
+ ? triggerPriceValue
736
+ : null,
737
+ quantity: Number.isFinite(quantityValue) && quantityValue >= 0 ? quantityValue : null,
738
+ closePosition,
739
+ };
740
+ }
741
+ catch (err) {
742
+ noteBinanceError(err);
743
+ logger.warn(TAG, `queryAlgoOrderProof(${clientAlgoId}) → null/unknown (NOT a verdict): ${formatError(err)}`);
744
+ return null;
745
+ }
746
+ }
685
747
  /** Fetch historical fills (trades) for a symbol via GET /fapi/v1/userTrades.
686
748
  * Used by the REST gap-filler (rest-gap-filler.ts) to backfill any fills
687
749
  * that landed during a WS blind window — when the user-data stream was
@@ -831,6 +893,9 @@ export class BinancePrivateApi {
831
893
  timestamp: raw.timestamp ?? Date.now(),
832
894
  datetime: raw.datetime ?? new Date().toISOString(),
833
895
  timeInForce: raw.timeInForce ?? 'GTC',
896
+ ...(typeof raw.clientOrderId === 'string' && raw.clientOrderId.length > 0
897
+ ? { clientOrderId: raw.clientOrderId }
898
+ : {}),
834
899
  };
835
900
  }
836
901
  /** Fetch a single order by ID. Used for fill polling and idempotency checks. */
@@ -871,39 +936,84 @@ export class BinancePrivateApi {
871
936
  * after network timeouts — check if the order went through before retrying.
872
937
  */
873
938
  async fetchOrderByClientId(clientOrderId, symbol) {
939
+ const resolution = await this.resolveOrderByClientId(clientOrderId, symbol);
940
+ return resolution.status === 'found' ? resolution.order : null;
941
+ }
942
+ /** Resolve a deterministic client id without treating read failures as absence. */
943
+ async resolveOrderByClientId(clientOrderId, symbol) {
874
944
  try {
875
- // Pre-gate: never hammer Binance during a ban. Caveat: during a ban
876
- // this returns null (= "not found"), so an idempotency caller could
877
- // resubmit. Accepted tradeoff — extending the IP ban for everyone is
878
- // worse, and the resubmit path is itself reduce-only/bounded.
945
+ // Never hammer Binance during a ban. A blocked read is caught below as
946
+ // UNKNOWN, so it can never authorize a resubmission.
879
947
  assertNotBanned('fetchOrder');
880
948
  const raw = await this.exchange.fetchOrder(undefined, symbol, {
881
949
  origClientOrderId: clientOrderId,
882
950
  });
883
951
  noteSuccess();
952
+ const canonicalSymbol = (value) => value.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
953
+ const rawId = raw?.id === undefined || raw?.id === null ? '' : String(raw.id);
954
+ const amount = raw?.amount;
955
+ const filled = raw?.filled;
956
+ const remaining = raw?.remaining;
957
+ if (!raw
958
+ || rawId.length === 0
959
+ || rawId.length > 512
960
+ || rawId.trim() !== rawId
961
+ || raw.clientOrderId !== clientOrderId
962
+ || typeof raw.symbol !== 'string'
963
+ || raw.symbol.length === 0
964
+ || canonicalSymbol(raw.symbol) !== canonicalSymbol(symbol)
965
+ || (raw.side !== 'buy' && raw.side !== 'sell')
966
+ || (raw.type !== 'market' && raw.type !== 'limit')
967
+ || (raw.status !== 'open' && raw.status !== 'closed' && raw.status !== 'canceled')
968
+ || typeof amount !== 'number'
969
+ || !Number.isFinite(amount)
970
+ || amount <= 0
971
+ || typeof filled !== 'number'
972
+ || !Number.isFinite(filled)
973
+ || filled < 0
974
+ || filled > amount + 1e-9 * Math.max(1, amount)
975
+ || typeof remaining !== 'number'
976
+ || !Number.isFinite(remaining)
977
+ || remaining < 0
978
+ || remaining > amount + 1e-9 * Math.max(1, amount)) {
979
+ return { status: 'unknown', detail: 'exchange returned a malformed client-order lookup response' };
980
+ }
884
981
  return {
885
- id: String(raw.id),
886
- symbol: raw.symbol ?? symbol,
887
- side: (raw.side ?? 'buy'),
888
- type: (raw.type ?? 'limit'),
889
- status: raw.status,
890
- amount: raw.amount ?? 0,
891
- filled: raw.filled ?? 0,
892
- remaining: raw.remaining ?? 0,
893
- average: raw.average ?? null,
894
- price: raw.price ?? null,
895
- cost: raw.cost ?? 0,
896
- fee: raw.fee ?? { cost: 0, currency: 'USDT' },
897
- timestamp: raw.timestamp ?? Date.now(),
898
- datetime: raw.datetime ?? new Date().toISOString(),
899
- timeInForce: raw.timeInForce ?? 'GTC',
982
+ status: 'found',
983
+ order: {
984
+ id: rawId,
985
+ symbol: raw.symbol,
986
+ side: raw.side,
987
+ type: raw.type,
988
+ status: raw.status,
989
+ amount,
990
+ filled,
991
+ remaining,
992
+ average: raw.average ?? null,
993
+ price: raw.price ?? null,
994
+ cost: raw.cost ?? 0,
995
+ fee: raw.fee ?? { cost: 0, currency: 'USDT' },
996
+ timestamp: raw.timestamp ?? Date.now(),
997
+ datetime: raw.datetime ?? new Date().toISOString(),
998
+ timeInForce: raw.timeInForce ?? 'GTC',
999
+ clientOrderId: raw.clientOrderId,
1000
+ ...(raw.info && typeof raw.info === 'object' ? { info: raw.info } : {}),
1001
+ },
900
1002
  };
901
1003
  }
902
1004
  catch (err) {
903
1005
  noteBinanceError(err);
904
- // Order not found is expected when checking idempotency — don't log as error
905
- logger.info(TAG, `fetchOrderByClientId(${clientOrderId}) not found or failed: ${formatError(err)}`);
906
- return null;
1006
+ const candidate = err;
1007
+ const message = formatError(err);
1008
+ const messageCode = message.match(/"code"\s*:\s*(-?\d+)/)?.[1];
1009
+ const directCode = typeof candidate?.code === 'number' ? candidate.code : undefined;
1010
+ const code = directCode ?? (messageCode ? Number(messageCode) : undefined);
1011
+ if (code === -2013) {
1012
+ logger.info(TAG, `resolveOrderByClientId(${clientOrderId}) confirmed absent (-2013)`);
1013
+ return { status: 'confirmed_absent' };
1014
+ }
1015
+ logger.warn(TAG, `resolveOrderByClientId(${clientOrderId}) outcome unknown: ${message}`);
1016
+ return { status: 'unknown', detail: message };
907
1017
  }
908
1018
  }
909
1019
  /** Cancel a real order on the exchange. Binance Futures splits orders across
@@ -40,6 +40,12 @@ export interface PluginConfigFile {
40
40
  * carry `venue` through or it resets to the binance default. */
41
41
  exchange?: ExchangeConfig;
42
42
  tradingMode?: TradingMode;
43
+ /** Hyperliquid-specific knobs (Phase 3). `marketSlippagePct` bounds the IOC
44
+ * "market" order — HL has no native market order, and ccxt's 5% default is far
45
+ * too loose to ever rely on. Clamped to ≤2% adapter-side. */
46
+ hl?: {
47
+ marketSlippagePct?: number;
48
+ };
43
49
  microLive?: {
44
50
  sizeCapPercent?: number;
45
51
  maxPositionUSDT?: number;
@@ -17,6 +17,9 @@ export const UNGOVERNABLE_TOOLS = new Set([
17
17
  // Core safety floor — protecting/exiting positions and the review capture.
18
18
  'create_order',
19
19
  'close_position',
20
+ // Reversal capabilities for existing Wave 9 positions come only from this
21
+ // status path, so central tool preferences must never disable it.
22
+ 'get_wave9_status',
20
23
  'modify_stop',
21
24
  'attach_brackets',
22
25
  'audit_bracket_protection',
@@ -1,5 +1,15 @@
1
1
  import type { CcxtOrder, CcxtBalance, CcxtPosition, TradingMode } from './types.js';
2
2
  import type { PositionMetadata, CloseReason } from './simulator/types.js';
3
+ /** Deterministic client-order lookup used after an outcome-ambiguous submit. */
4
+ export type ClientOrderResolution = {
5
+ status: 'found';
6
+ order: CcxtOrder;
7
+ } | {
8
+ status: 'confirmed_absent';
9
+ } | {
10
+ status: 'unknown';
11
+ detail: string;
12
+ };
3
13
  /** Adapter readiness state machine: INIT_PENDING → READY | DEGRADED | BLOCKED */
4
14
  export type AdapterReadiness = 'INIT_PENDING' | 'READY' | 'DEGRADED' | 'BLOCKED';
5
15
  /** Options for order submission. */
@@ -42,6 +52,12 @@ export interface IExchangeAdapter {
42
52
  getPositionsOrNull(symbol?: string): Promise<CcxtPosition[] | null>;
43
53
  getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
44
54
  fetchOrder(orderId: string, symbol?: string): Promise<CcxtOrder | null>;
55
+ /**
56
+ * A null-style fetch is unsafe for entry recovery because network/auth/rate
57
+ * failures are not proof an order was absent. Live adapters expose this
58
+ * tri-state resolver; paper adapters may omit it.
59
+ */
60
+ resolveOrderByClientId?(clientOrderId: string, symbol: string): Promise<ClientOrderResolution>;
45
61
  getLastPrice(symbol: string): Promise<number | null>;
46
62
  readonly readiness: AdapterReadiness;
47
63
  readonly mode: TradingMode;