@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
@@ -0,0 +1,796 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { applyWave9AdmissionBatch, } from '../portfolio/wave9-admission.js';
3
+ import { DAY_MS, evaluateWave9Daily, WAVE9_LONG_STRATEGY, WAVE9_SHORT_STRATEGY, WAVE9_SYMBOL_PRIORITY, } from '../portfolio/wave9-policy.js';
4
+ import { FOUR_HOURS_MS, createWave9UsdmEvidenceProvider, } from '../wave9/usdm-evidence-provider.js';
5
+ import { isWave9ManagedPosition } from '../wave9/paper-admission-guard.js';
6
+ import { fetchIntelApi } from './intel-api.js';
7
+ export const WAVE9_BUNDLE_NAME = 'wave9_28d_momentum_reversal';
8
+ export const WAVE9_EXECUTION_KERNEL = 'wave9_shared_v1';
9
+ export const WAVE9_PAPER_ENTRY_WINDOW_MS = 10 * 60_000;
10
+ export class IntelWave9ActivationClient {
11
+ deps;
12
+ constructor(deps) {
13
+ this.deps = deps;
14
+ }
15
+ async getActivation() {
16
+ const result = await fetchIntelApi('/api/wave9/activation', this.deps);
17
+ if ('error' in result) {
18
+ return {
19
+ available: false,
20
+ name: WAVE9_BUNDLE_NAME,
21
+ executionModel: WAVE9_EXECUTION_KERNEL,
22
+ active: false,
23
+ error: String(result.error),
24
+ };
25
+ }
26
+ const bundle = result.bundle;
27
+ if (!bundle || typeof bundle !== 'object' || Array.isArray(bundle)) {
28
+ return invalidActivation('response did not contain a bundle object');
29
+ }
30
+ const row = bundle;
31
+ if (row.name !== WAVE9_BUNDLE_NAME
32
+ || row.executionModel !== WAVE9_EXECUTION_KERNEL
33
+ || typeof row.active !== 'boolean'
34
+ || (row.source !== undefined
35
+ && row.source !== 'tenant_override'
36
+ && row.source !== 'global_default')) {
37
+ return invalidActivation('response bundle failed strict validation');
38
+ }
39
+ return {
40
+ available: true,
41
+ name: WAVE9_BUNDLE_NAME,
42
+ executionModel: WAVE9_EXECUTION_KERNEL,
43
+ active: row.active,
44
+ ...(row.source ? { source: row.source } : {}),
45
+ };
46
+ }
47
+ }
48
+ function invalidActivation(reason) {
49
+ return {
50
+ available: false,
51
+ name: WAVE9_BUNDLE_NAME,
52
+ executionModel: WAVE9_EXECUTION_KERNEL,
53
+ active: false,
54
+ error: `Invalid Wave 9 activation response: ${reason}`,
55
+ };
56
+ }
57
+ function isExecutableMode(mode) {
58
+ return mode === 'PAPER' || mode === 'LIVE';
59
+ }
60
+ function canonicalSymbol(symbol) {
61
+ return symbol.toUpperCase().replace(/[^A-Z0-9]/g, '').replace(/USDTUSDT$/, 'USDT');
62
+ }
63
+ function executionSymbol(symbol) {
64
+ const canonical = canonicalSymbol(symbol);
65
+ if (!canonical.endsWith('USDT'))
66
+ throw new Error(`unsupported Wave 9 execution symbol ${symbol}`);
67
+ return `${canonical.slice(0, -4)}/USDT:USDT`;
68
+ }
69
+ function isWave9Position(position) {
70
+ return isWave9ManagedPosition(position);
71
+ }
72
+ export function wave9MissionId(candidateId) {
73
+ return `wave9_${createHash('sha256').update(candidateId).digest('hex').slice(0, 32)}`;
74
+ }
75
+ function nearlyEqual(left, right) {
76
+ return Math.abs(left - right) <= 1e-9 * Math.max(1, Math.abs(left), Math.abs(right));
77
+ }
78
+ function requireBatch(batch, eventMs) {
79
+ if (batch.venue !== 'binance_usdm'
80
+ || batch.marketType !== 'future'
81
+ || batch.eventTime.getTime() !== eventMs
82
+ || batch.symbols.length !== WAVE9_SYMBOL_PRIORITY.length) {
83
+ throw new Error('Wave 9 atomic evidence identity mismatch');
84
+ }
85
+ for (const [index, row] of batch.symbols.entries()) {
86
+ if (row.symbol !== WAVE9_SYMBOL_PRIORITY[index]) {
87
+ throw new Error('Wave 9 atomic evidence universe/order mismatch');
88
+ }
89
+ const opening = row.fourHourBars.find((bar) => bar.time.getTime() === eventMs);
90
+ const completed = row.fourHourBars.find((bar) => bar.time.getTime() === eventMs - FOUR_HOURS_MS);
91
+ if (!opening || !completed) {
92
+ throw new Error(`Wave 9 4h boundary evidence missing for ${row.symbol}`);
93
+ }
94
+ }
95
+ }
96
+ function controlState(activation, snapshot, wave9Positions) {
97
+ if (activation.available && activation.active) {
98
+ return { state: 'active', reason: 'tenant_bundle_active' };
99
+ }
100
+ if (wave9Positions.length > 0) {
101
+ return { state: 'exit_only', reason: 'bundle_inactive_with_open_wave9_position' };
102
+ }
103
+ if (!activation.available || !snapshot.positionsAvailable) {
104
+ return {
105
+ state: 'exit_only',
106
+ reason: !activation.available
107
+ ? 'activation_unavailable_fail_closed'
108
+ : 'positions_unavailable_fail_closed',
109
+ };
110
+ }
111
+ return { state: 'inactive', reason: 'tenant_bundle_inactive_and_flat' };
112
+ }
113
+ function canonicalAdmissionBook(snapshot) {
114
+ const reasons = [];
115
+ const limitations = [
116
+ 'failed_attempt_event_latch_is_process_local_and_resets_on_restart',
117
+ ];
118
+ if (snapshot.tradingMode === 'PAPER') {
119
+ limitations.push('market_mark_fingerprint_drift_requires_immediate_status_recall', 'paper_funding_not_booked', 'process_local_unlocked_simulator', 'cross_process_lease_absent_duplicate_concurrent_admission_not_ruled_out');
120
+ }
121
+ else {
122
+ limitations.push('live_candidate_identity_uses_local_durable_execution_ledger', 'non_wave9_positions_are_enforced_by_the_generic_live_pretrade_gate', 'live_funding_is_not_attributed_per_wave9_position');
123
+ }
124
+ if (!isExecutableMode(snapshot.tradingMode)) {
125
+ reasons.push('Wave 9 execution supports PAPER and LIVE only');
126
+ }
127
+ if (snapshot.tradingMode === 'LIVE'
128
+ && snapshot.nativeStopProtectionAvailable !== true) {
129
+ reasons.push('native stop protection is unavailable for LIVE Wave 9 execution');
130
+ }
131
+ if (!snapshot.positionsAvailable || !snapshot.positions) {
132
+ reasons.push('locally complete account positions are unavailable');
133
+ }
134
+ if (!snapshot.openOrdersAvailable || snapshot.openOrderCount === undefined) {
135
+ reasons.push('locally complete open-order state is unavailable');
136
+ }
137
+ else if (!Number.isInteger(snapshot.openOrderCount) || snapshot.openOrderCount < 0) {
138
+ reasons.push('open-order count is invalid');
139
+ }
140
+ else if (snapshot.openOrderCount > 0) {
141
+ reasons.push('pending exposure-increasing orders make admission geometry ambiguous');
142
+ }
143
+ if (!snapshot.cashEquityAvailable
144
+ || snapshot.cashEquityUsd === undefined
145
+ || !Number.isFinite(snapshot.cashEquityUsd)
146
+ || snapshot.cashEquityUsd <= 0) {
147
+ reasons.push('locally complete positive wallet cash is unavailable');
148
+ }
149
+ if (!snapshot.accountFingerprint
150
+ || !/^[a-f0-9]{64}$/.test(snapshot.accountFingerprint)) {
151
+ reasons.push('valid account fingerprint is unavailable');
152
+ }
153
+ const observed = snapshot.positions ?? [];
154
+ const nonWave9 = observed.filter((position) => !isWave9Position(position));
155
+ if (snapshot.tradingMode === 'PAPER' && nonWave9.length > 0) {
156
+ reasons.push('non-Wave 9 positions make isolated-book projection ambiguous');
157
+ }
158
+ const positions = [];
159
+ for (const position of observed.filter(isWave9Position)) {
160
+ const symbol = canonicalSymbol(position.symbol);
161
+ const direction = position.side === 'long' ? 'LONG' : 'SHORT';
162
+ const expectedStrategy = direction === 'LONG' ? WAVE9_LONG_STRATEGY : WAVE9_SHORT_STRATEGY;
163
+ if (snapshot.tradingMode === 'LIVE' && position.nativeStop?.verified !== true) {
164
+ reasons.push(`verified exchange-native stop unavailable for ${position.missionId ?? symbol}`
165
+ + `:${position.nativeStop?.reason ?? 'native_stop_missing'}`);
166
+ continue;
167
+ }
168
+ if (!position.candidateId
169
+ || !position.missionId
170
+ || position.missionId !== wave9MissionId(position.candidateId)
171
+ || position.strategy !== expectedStrategy
172
+ || !WAVE9_SYMBOL_PRIORITY.includes(symbol)
173
+ || position.stopPrice === undefined
174
+ || !Number.isFinite(position.stopPrice)
175
+ || (position.accruedFundingUsd !== undefined
176
+ && !Number.isFinite(position.accruedFundingUsd))
177
+ || !Number.isFinite(position.quantity)
178
+ || position.quantity <= 0
179
+ || !Number.isFinite(position.entryPrice)
180
+ || position.entryPrice <= 0
181
+ || !Number.isFinite(position.markPrice)
182
+ || position.markPrice <= 0) {
183
+ reasons.push(`incomplete durable Wave 9 geometry for ${position.missionId ?? symbol}`);
184
+ continue;
185
+ }
186
+ positions.push({
187
+ candidateId: position.candidateId,
188
+ symbol,
189
+ strategy: position.strategy,
190
+ direction,
191
+ entryPrice: position.entryPrice,
192
+ quantity: position.quantity,
193
+ markPrice: position.markPrice,
194
+ initialStop: position.stopPrice,
195
+ dollarRiskUsd: Math.abs(position.entryPrice - position.stopPrice) * position.quantity,
196
+ // PAPER does not book per-position funding and the LIVE execution
197
+ // ledger does not yet attribute it, so both capture paths expose zero.
198
+ accruedFundingUsd: position.accruedFundingUsd ?? 0,
199
+ });
200
+ }
201
+ if (positions.length !== observed.filter(isWave9Position).length) {
202
+ reasons.push('not every observed Wave 9 position has complete local admission geometry');
203
+ }
204
+ return {
205
+ available: reasons.length === 0,
206
+ ...(snapshot.cashEquityUsd !== undefined ? { cashEquityUsd: snapshot.cashEquityUsd } : {}),
207
+ positions,
208
+ reasons: [...new Set(reasons)],
209
+ limitations: [...new Set([...(snapshot.reasons ?? []), ...limitations])],
210
+ };
211
+ }
212
+ function candidateFor(symbol, eventTime, openingPrice, entry) {
213
+ return {
214
+ id: `${eventTime}|${symbol}|${entry.strategy}|${entry.direction}`,
215
+ eventTime,
216
+ symbol,
217
+ strategy: entry.strategy,
218
+ direction: entry.direction,
219
+ referencePrice: openingPrice,
220
+ stopPrice: entry.stopPrice,
221
+ };
222
+ }
223
+ function decisionSummary(decision) {
224
+ return {
225
+ candidateId: decision.id,
226
+ missionId: wave9MissionId(decision.id),
227
+ accepted: decision.accepted,
228
+ reason: decision.reason,
229
+ direction: decision.direction,
230
+ referenceOpen: decision.referencePrice,
231
+ modeledEntryPrice: decision.entryPrice ?? null,
232
+ quantity: decision.quantity ?? null,
233
+ stopPrice: decision.stopPrice,
234
+ targets: [],
235
+ targetPolicy: 'none_completed_daily_signal_reversal',
236
+ projectedOpenPositions: decision.projectedOpenPositions ?? null,
237
+ projectedGrossExposureUsd: decision.projectedGrossExposureUsd ?? null,
238
+ projectedOpenRiskUsd: decision.projectedOpenRiskUsd ?? null,
239
+ projectedLiquidationEquityUsd: decision.projectedLiquidationEquityUsd ?? null,
240
+ };
241
+ }
242
+ function batchId(eventTime, decisions, accountFingerprint, tradingMode) {
243
+ const identity = decisions.map((decision) => decision.id).join('\n');
244
+ return `wave9_batch_${createHash('sha256')
245
+ .update(`${eventTime}\n${tradingMode}\n${accountFingerprint}\n${identity}`)
246
+ .digest('hex').slice(0, 24)}`;
247
+ }
248
+ function authorizationAbsenceMeaning(state, modeSupported, venueSupported) {
249
+ if (!venueSupported)
250
+ return 'strategy_unavailable_on_current_venue';
251
+ if (!modeSupported)
252
+ return 'strategy_unavailable_in_current_mode';
253
+ if (state === 'inactive')
254
+ return 'strategy_disabled_by_operator_toggle';
255
+ if (state === 'exit_only')
256
+ return 'no_new_entries_exit_management_only';
257
+ return 'wait_strategy_remains_enabled';
258
+ }
259
+ function baseStatus(activation, snapshot, state, observedAt, executionVenue, clockValid) {
260
+ const modeSupported = isExecutableMode(snapshot.tradingMode);
261
+ const venueSupported = executionVenue === 'binance';
262
+ const runtimeReadinessFailures = [];
263
+ if (!clockValid)
264
+ runtimeReadinessFailures.push('status_clock_invalid');
265
+ if (!venueSupported)
266
+ runtimeReadinessFailures.push('unsupported_execution_venue');
267
+ if (!modeSupported)
268
+ runtimeReadinessFailures.push('unsupported_trading_mode');
269
+ if (state.state === 'inactive') {
270
+ runtimeReadinessFailures.push('strategy_disabled_by_operator_toggle');
271
+ }
272
+ else if (state.state === 'exit_only') {
273
+ runtimeReadinessFailures.push(`control_state_exit_only:${state.reason}`);
274
+ }
275
+ if (!snapshot.positionsAvailable)
276
+ runtimeReadinessFailures.push('account_positions_unavailable');
277
+ if (!snapshot.openOrdersAvailable)
278
+ runtimeReadinessFailures.push('account_open_orders_unavailable');
279
+ if (!snapshot.cashEquityAvailable)
280
+ runtimeReadinessFailures.push('account_cash_equity_unavailable');
281
+ if (snapshot.tradingMode === 'LIVE') {
282
+ if (snapshot.adapterReadiness !== 'READY') {
283
+ runtimeReadinessFailures.push(`live_adapter_not_ready:${snapshot.adapterReadiness ?? 'unknown'}`);
284
+ }
285
+ if (snapshot.nativeStopProtectionAvailable !== true) {
286
+ runtimeReadinessFailures.push('live_native_stop_protection_unavailable');
287
+ }
288
+ }
289
+ const runtimeReadyForNewEntryAuthorization = runtimeReadinessFailures.length === 0;
290
+ const runtimeReadinessReasons = runtimeReadyForNewEntryAuthorization
291
+ ? [snapshot.tradingMode === 'LIVE'
292
+ ? 'live_adapter_account_and_native_stops_ready'
293
+ : 'paper_account_snapshot_ready']
294
+ : [...new Set(runtimeReadinessFailures)];
295
+ const strategyEnabled = activation.available && activation.active;
296
+ const liveCapabilityEnabled = strategyEnabled
297
+ && state.state === 'active'
298
+ && venueSupported
299
+ && snapshot.tradingMode === 'LIVE';
300
+ const effectiveStatus = !venueSupported
301
+ ? 'unsupported_venue'
302
+ : !modeSupported
303
+ ? 'unsupported_mode'
304
+ : state.state === 'inactive'
305
+ ? 'disabled_by_operator_toggle'
306
+ : state.state === 'exit_only'
307
+ ? 'exit_only'
308
+ : snapshot.tradingMode === 'LIVE'
309
+ ? 'live_enabled'
310
+ : 'paper_enabled';
311
+ const missingCurrentAuthorizationMeans = authorizationAbsenceMeaning(state.state, modeSupported, venueSupported);
312
+ return {
313
+ kind: 'wave9_agent_status',
314
+ statusContractVersion: 2,
315
+ bundle: activation,
316
+ controlState: state.state,
317
+ controlStateReason: state.reason,
318
+ executionKernel: WAVE9_EXECUTION_KERNEL,
319
+ tradeability: {
320
+ effectiveStatus,
321
+ strategyEnabled,
322
+ operatorToggleActive: strategyEnabled,
323
+ venue: executionVenue,
324
+ venueSupported,
325
+ currentMode: snapshot.tradingMode,
326
+ supportedModes: ['PAPER', 'LIVE'],
327
+ currentModeSupported: modeSupported,
328
+ adapterReadiness: snapshot.adapterReadiness ?? null,
329
+ wave9PositionCount: snapshot.positionsAvailable
330
+ ? (snapshot.positions ?? []).filter(isWave9Position).length
331
+ : null,
332
+ liveCapabilityEnabled,
333
+ usesRealFunds: snapshot.tradingMode === 'LIVE' || snapshot.tradingMode === 'MICRO_LIVE',
334
+ runtimeReadyForNewEntryAuthorization,
335
+ runtimeReadinessReasons,
336
+ authorizationModel: 'short_lived_exact_token',
337
+ missingCurrentAuthorizationMeans,
338
+ },
339
+ currentActionAuthorization: {
340
+ entry: 'none',
341
+ exit: 'none',
342
+ entryCount: 0,
343
+ exitCount: 0,
344
+ entryReason: 'not_evaluated',
345
+ exitReason: 'not_evaluated',
346
+ noneMeaning: missingCurrentAuthorizationMeans,
347
+ },
348
+ statusCall: {
349
+ mutatesOrders: false,
350
+ returnedAuthorizationsMayAuthorizeOrders: true,
351
+ },
352
+ observedAt,
353
+ accountProvenance: {
354
+ source: snapshot.source,
355
+ provenance: snapshot.provenance,
356
+ authoritative: snapshot.authoritative,
357
+ accountFingerprint: snapshot.accountFingerprint ?? null,
358
+ },
359
+ };
360
+ }
361
+ function currentActionAuthorization(entryCount, exitCount, entryReason, exitReason, noneMeaning) {
362
+ return {
363
+ entry: entryCount > 0 ? 'authorized' : 'none',
364
+ exit: exitCount > 0 ? 'authorized' : 'none',
365
+ entryCount,
366
+ exitCount,
367
+ entryReason,
368
+ exitReason,
369
+ noneMeaning,
370
+ };
371
+ }
372
+ function requiredOperatorReport(base, action) {
373
+ const tradeability = base.tradeability;
374
+ const currentMode = String(tradeability.currentMode ?? 'UNKNOWN');
375
+ const effectiveStatus = String(tradeability.effectiveStatus ?? 'unknown');
376
+ const liveCapabilityEnabled = tradeability.liveCapabilityEnabled === true;
377
+ const usesRealFunds = tradeability.usesRealFunds === true;
378
+ const runtimeReady = tradeability.runtimeReadyForNewEntryAuthorization === true;
379
+ const runtimeReadinessReasons = Array.isArray(tradeability.runtimeReadinessReasons)
380
+ ? tradeability.runtimeReadinessReasons.map(String)
381
+ : ['runtime_readiness_reason_unavailable'];
382
+ const entryCount = Number(action.entryCount ?? 0);
383
+ const exitCount = Number(action.exitCount ?? 0);
384
+ const entryReason = String(action.entryReason ?? 'unknown');
385
+ const exitReason = String(action.exitReason ?? 'unknown');
386
+ const liveCapabilityLine = currentMode === 'LIVE' && liveCapabilityEnabled
387
+ ? 'Wave 9 is enabled for LIVE trading.'
388
+ : `Wave 9 LIVE capability: ${liveCapabilityEnabled ? 'enabled' : 'not enabled'} (effective status: ${effectiveStatus}; current mode: ${currentMode}).`;
389
+ return {
390
+ contractVersion: 1,
391
+ instruction: 'Reproduce every line below in the operator reply. Do not omit, merge, or paraphrase away the labels, counts, or reasons.',
392
+ lines: [
393
+ liveCapabilityLine,
394
+ `Current mode: ${currentMode}; real funds: ${usesRealFunds ? 'yes' : 'no'}.`,
395
+ `Runtime readiness for new entry authorization: ${runtimeReady ? 'READY' : 'NOT READY'} (${runtimeReady}; reasons: ${runtimeReadinessReasons.join(', ')}).`,
396
+ `Current entry authorizations: ${entryCount} (reason: ${entryReason}).`,
397
+ `Current exit authorizations: ${exitCount} (reason: ${exitReason}).`,
398
+ ],
399
+ readOnlyBoundary: 'Reporting status alone does not authorize placing or closing an order or toggling a strategy.',
400
+ };
401
+ }
402
+ function statusWithCurrentAction(base, action) {
403
+ return {
404
+ ...base,
405
+ currentActionAuthorization: action,
406
+ requiredOperatorReport: requiredOperatorReport(base, action),
407
+ };
408
+ }
409
+ export async function getWave9StatusTool(deps) {
410
+ const now = (deps.now ?? (() => new Date()))();
411
+ const nowMs = now.getTime();
412
+ const clockValid = Number.isFinite(nowMs);
413
+ const activation = await deps.activationClient.getActivation();
414
+ const preflightSnapshot = await deps.operationLock.withAccountLock('wave9-status-preflight', () => deps.getAccountSnapshot());
415
+ const observedPositions = preflightSnapshot.positionsAvailable
416
+ ? (preflightSnapshot.positions ?? [])
417
+ : [];
418
+ const wave9Positions = observedPositions.filter(isWave9Position);
419
+ const state = controlState(activation, preflightSnapshot, wave9Positions);
420
+ const base = baseStatus(activation, preflightSnapshot, state, clockValid ? now.toISOString() : null, deps.executionVenue, clockValid);
421
+ if (!clockValid) {
422
+ return {
423
+ ...statusWithCurrentAction(base, currentActionAuthorization(0, 0, 'invalid_status_clock', 'invalid_status_clock', authorizationAbsenceMeaning(state.state, isExecutableMode(preflightSnapshot.tradingMode), deps.executionVenue === 'binance'))),
424
+ error: 'Wave 9 status clock is invalid',
425
+ evidenceAvailable: false,
426
+ evaluationSkipped: 'invalid_status_clock',
427
+ decisions: [],
428
+ authorizations: { entries: [], exits: [] },
429
+ };
430
+ }
431
+ if (deps.executionVenue !== 'binance') {
432
+ return {
433
+ ...statusWithCurrentAction(base, currentActionAuthorization(0, 0, 'unsupported_execution_venue', 'unsupported_execution_venue', 'strategy_unavailable_on_current_venue')),
434
+ evidenceAvailable: false,
435
+ evaluationSkipped: 'unsupported_execution_venue',
436
+ venue: deps.executionVenue,
437
+ reason: 'Wave 9 is pinned to Binance USD-M futures and is unavailable on this execution venue.',
438
+ decisions: [],
439
+ authorizations: { entries: [], exits: [] },
440
+ };
441
+ }
442
+ if (!isExecutableMode(preflightSnapshot.tradingMode)) {
443
+ return {
444
+ ...statusWithCurrentAction(base, currentActionAuthorization(0, 0, 'unsupported_trading_mode', 'unsupported_trading_mode', 'strategy_unavailable_in_current_mode')),
445
+ evidenceAvailable: false,
446
+ evaluationSkipped: 'unsupported_trading_mode',
447
+ reason: 'Wave 9 authorizes exact PAPER or LIVE execution only; SHADOW and MICRO_LIVE are unsupported.',
448
+ accountAdmission: canonicalAdmissionBook(preflightSnapshot),
449
+ decisions: [],
450
+ authorizations: { entries: [], exits: [] },
451
+ };
452
+ }
453
+ // Inactive + authoritatively flat is a true no-market-construction path.
454
+ if (state.state === 'inactive') {
455
+ return {
456
+ ...statusWithCurrentAction(base, currentActionAuthorization(0, 0, 'disabled_by_operator_toggle', 'no_managed_position', 'strategy_disabled_by_operator_toggle')),
457
+ evidenceAvailable: false,
458
+ evaluationSkipped: 'bundle_inactive_and_flat',
459
+ decisions: [],
460
+ authorizations: { entries: [], exits: [] },
461
+ };
462
+ }
463
+ const eventMs = Math.floor(nowMs / DAY_MS) * DAY_MS;
464
+ const eventTime = new Date(eventMs).toISOString();
465
+ const entryDeadlineMs = eventMs + WAVE9_PAPER_ENTRY_WINDOW_MS;
466
+ const entryWindowStatus = nowMs < entryDeadlineMs ? 'open' : 'missed_open';
467
+ let batch;
468
+ try {
469
+ const provider = (deps.createEvidenceProvider ?? createWave9UsdmEvidenceProvider)();
470
+ batch = await provider.fetchAtomicBatch(new Date(eventMs));
471
+ requireBatch(batch, eventMs);
472
+ }
473
+ catch (error) {
474
+ return {
475
+ ...statusWithCurrentAction(base, currentActionAuthorization(0, 0, 'evidence_unavailable', 'evidence_unavailable', authorizationAbsenceMeaning(state.state, true, true))),
476
+ eventTime,
477
+ evidenceAvailable: false,
478
+ evidenceError: error instanceof Error ? error.message : String(error),
479
+ entryWindow: {
480
+ status: entryWindowStatus,
481
+ opensAt: eventTime,
482
+ closesAt: new Date(entryDeadlineMs).toISOString(),
483
+ observedDelayMs: nowMs - eventMs,
484
+ },
485
+ decisions: [],
486
+ authorizations: { entries: [], exits: [] },
487
+ };
488
+ }
489
+ const evaluate = deps.evaluateDaily ?? evaluateWave9Daily;
490
+ const dailyBySymbol = new Map();
491
+ const candidates = [];
492
+ try {
493
+ for (const row of batch.symbols) {
494
+ const daily = evaluate(row.dailyBars, new Date(eventMs));
495
+ const completed = row.fourHourBars.find((bar) => bar.time.getTime() === eventMs - FOUR_HOURS_MS);
496
+ if (!nearlyEqual(daily.referencePrice, completed.close)) {
497
+ throw new Error(`${row.symbol} completed-daily close does not match final completed 4h close`);
498
+ }
499
+ const opening = row.fourHourBars.find((bar) => bar.time.getTime() === eventMs).open;
500
+ dailyBySymbol.set(row.symbol, daily);
501
+ for (const entry of daily.entries) {
502
+ candidates.push(candidateFor(row.symbol, eventTime, opening, entry));
503
+ }
504
+ }
505
+ }
506
+ catch (error) {
507
+ return {
508
+ ...statusWithCurrentAction(base, currentActionAuthorization(0, 0, 'evaluation_failed', 'evaluation_failed', authorizationAbsenceMeaning(state.state, true, true))),
509
+ eventTime,
510
+ evidenceAvailable: false,
511
+ evidenceError: error instanceof Error ? error.message : String(error),
512
+ decisions: [],
513
+ authorizations: { entries: [], exits: [] },
514
+ };
515
+ }
516
+ // Market I/O stays outside the trading lock. Re-capture the account
517
+ // and issue all short-lived authorizations inside one serialized section so
518
+ // a watcher/tool mutation cannot race snapshot-to-token binding.
519
+ return deps.operationLock.withAccountLock('wave9-status-snapshot-and-authorize', async () => {
520
+ const snapshot = await deps.getAccountSnapshot();
521
+ const lockedObservedPositions = snapshot.positionsAvailable ? (snapshot.positions ?? []) : [];
522
+ const lockedWave9Positions = lockedObservedPositions.filter(isWave9Position);
523
+ const lockedState = controlState(activation, snapshot, lockedWave9Positions);
524
+ const lockedBase = baseStatus(activation, snapshot, lockedState, now.toISOString(), deps.executionVenue, true);
525
+ const book = canonicalAdmissionBook(snapshot);
526
+ const alreadyExecutedThisEvent = new Set((snapshot.executedCandidateIds ?? []).filter((candidateId) => typeof candidateId === 'string' && candidateId.startsWith(`${eventTime}|`)));
527
+ const occupiedNonWave9Symbols = new Set(lockedObservedPositions
528
+ .filter((position) => !isWave9Position(position))
529
+ .map((position) => canonicalSymbol(position.symbol)));
530
+ const admissionCandidates = candidates.filter((candidate) => !alreadyExecutedThisEvent.has(candidate.id)
531
+ && !occupiedNonWave9Symbols.has(candidate.symbol));
532
+ // Reversal release is chronologically before same-event admission. When a
533
+ // held position must exit, issue only the exit capability and require the
534
+ // agent to re-call status after the close; never compute/authorize entries
535
+ // against a position that is logically released but still actually held.
536
+ const reversalDuePositions = lockedWave9Positions.filter((position) => {
537
+ const daily = dailyBySymbol.get(canonicalSymbol(position.symbol));
538
+ return daily
539
+ ? (position.side === 'long' ? daily.exitLong : daily.exitShort)
540
+ : false;
541
+ });
542
+ const exitInputs = [];
543
+ if (isExecutableMode(snapshot.tradingMode)
544
+ && deps.authorizationIssuer) {
545
+ for (const position of lockedWave9Positions) {
546
+ const symbol = canonicalSymbol(position.symbol);
547
+ const daily = dailyBySymbol.get(symbol);
548
+ if (!daily
549
+ || !position.missionId
550
+ || !position.candidateId
551
+ || !position.positionFingerprint
552
+ || !/^[a-f0-9]{64}$/.test(position.positionFingerprint))
553
+ continue;
554
+ const reversalDue = position.side === 'long' ? daily.exitLong : daily.exitShort;
555
+ if (!reversalDue)
556
+ continue;
557
+ exitInputs.push({
558
+ candidateId: position.candidateId,
559
+ missionId: position.missionId,
560
+ symbol,
561
+ positionSide: position.side,
562
+ reason: 'signal_reversal',
563
+ notBeforeMs: eventMs,
564
+ deadlineMs: eventMs + DAY_MS,
565
+ positionFingerprint: position.positionFingerprint,
566
+ tradingMode: snapshot.tradingMode,
567
+ });
568
+ }
569
+ }
570
+ let issuedExits = [];
571
+ if (exitInputs.length > 0) {
572
+ try {
573
+ issuedExits = [...deps.authorizationIssuer.issueExitBatch(exitInputs)];
574
+ }
575
+ catch {
576
+ // A malformed or stale recognized position must suppress admission, not
577
+ // crash the heartbeat/status path or expose an unusable authorization.
578
+ book.reasons.push('Wave 9 reversal authorization issuance failed closed');
579
+ }
580
+ }
581
+ const exitAuthorizationByMission = new Map(issuedExits.map((authorization) => [authorization.missionId, authorization]));
582
+ const pendingReversalRelease = reversalDuePositions.length > 0;
583
+ let admissionDecisions = [];
584
+ if (pendingReversalRelease) {
585
+ book.reasons.push('execute authorized signal-reversal exits, then re-call status before admission');
586
+ if (exitInputs.length !== reversalDuePositions.length
587
+ || issuedExits.length !== exitInputs.length) {
588
+ book.reasons.push('reversal_authorization_unavailable_for_one_or_more_due_positions');
589
+ }
590
+ }
591
+ else if (book.available && lockedState.state === 'active') {
592
+ try {
593
+ admissionDecisions = applyWave9AdmissionBatch({
594
+ cashEquityUsd: book.cashEquityUsd,
595
+ positions: book.positions,
596
+ }, admissionCandidates).decisions;
597
+ }
598
+ catch (error) {
599
+ book.available = false;
600
+ book.reasons.push(`shared admission kernel rejected account snapshot: ${error instanceof Error ? error.message : String(error)}`);
601
+ }
602
+ }
603
+ else if (lockedState.state !== 'active') {
604
+ book.reasons.push('new admissions disabled while bundle is inactive or exit-only');
605
+ }
606
+ const admissionById = new Map(admissionDecisions.map((decision) => [decision.id, decision]));
607
+ const entryInputs = [];
608
+ if (lockedState.state === 'active'
609
+ && isExecutableMode(snapshot.tradingMode)
610
+ && entryWindowStatus === 'open'
611
+ && book.available
612
+ && !pendingReversalRelease
613
+ && snapshot.accountFingerprint
614
+ && deps.authorizationIssuer) {
615
+ // Authorize exactly one mutation. Its real fill/fee changes both the
616
+ // fingerprint and the next candidate's sizing/caps, so later accepted
617
+ // modeled decisions require a fresh status call against the actual book.
618
+ const authorizationTradingMode = snapshot.tradingMode;
619
+ const accepted = admissionDecisions.filter((decision) => decision.accepted).slice(0, 1);
620
+ const id = batchId(eventTime, admissionDecisions, snapshot.accountFingerprint, authorizationTradingMode);
621
+ accepted.forEach((decision, sequence) => {
622
+ entryInputs.push({
623
+ batchId: id,
624
+ candidateId: decision.id,
625
+ missionId: wave9MissionId(decision.id),
626
+ symbol: decision.symbol,
627
+ side: decision.direction === 'LONG' ? 'BUY' : 'SELL',
628
+ quantity: String(decision.quantity),
629
+ stopPrice: String(decision.stopPrice),
630
+ sequence,
631
+ notBeforeMs: eventMs,
632
+ deadlineMs: entryDeadlineMs,
633
+ accountFingerprint: snapshot.accountFingerprint,
634
+ tradingMode: authorizationTradingMode,
635
+ });
636
+ });
637
+ }
638
+ const issuedEntries = entryInputs.length > 0
639
+ ? [...deps.authorizationIssuer.issueBatch(entryInputs)]
640
+ : [];
641
+ const entryAuthorizationByCandidate = new Map(issuedEntries.map((authorization) => [authorization.candidateId, authorization]));
642
+ const decisions = WAVE9_SYMBOL_PRIORITY.map((symbol) => {
643
+ const daily = dailyBySymbol.get(symbol);
644
+ const position = lockedWave9Positions.find((candidate) => canonicalSymbol(candidate.symbol) === symbol);
645
+ const rowCandidates = candidates.filter((candidate) => candidate.symbol === symbol);
646
+ const entries = rowCandidates.map((candidate) => {
647
+ const admission = admissionById.get(candidate.id);
648
+ const firstAuthorizedCandidateId = issuedEntries[0]?.candidateId;
649
+ const alreadyExecuted = alreadyExecutedThisEvent.has(candidate.id);
650
+ const occupiedByNonWave9Position = occupiedNonWave9Symbols.has(candidate.symbol);
651
+ const authorization = entryAuthorizationByCandidate.get(candidate.id);
652
+ return {
653
+ candidateId: candidate.id,
654
+ missionId: wave9MissionId(candidate.id),
655
+ executionSymbol: executionSymbol(candidate.symbol),
656
+ strategy: candidate.strategy,
657
+ direction: candidate.direction,
658
+ referenceOpen: candidate.referencePrice,
659
+ stopPrice: candidate.stopPrice,
660
+ targets: [],
661
+ targetPolicy: 'none_completed_daily_signal_reversal',
662
+ capDecision: occupiedByNonWave9Position
663
+ ? {
664
+ status: 'symbol_occupied_by_non_wave9_position',
665
+ reasons: ['the symbol already has a non-Wave 9 live position'],
666
+ }
667
+ : admission
668
+ ? {
669
+ ...decisionSummary(admission),
670
+ authorizationStage: !admission.accepted
671
+ ? 'rejected'
672
+ : admission.id === firstAuthorizedCandidateId
673
+ ? 'authorized_now'
674
+ : entryWindowStatus === 'missed_open'
675
+ ? 'missed_open_no_authorization'
676
+ : issuedEntries.length > 0
677
+ ? 'modeled_only_recall_after_prior_fill'
678
+ : 'authorization_unavailable',
679
+ }
680
+ : {
681
+ status: alreadyExecuted
682
+ ? 'already_executed_this_event'
683
+ : pendingReversalRelease
684
+ ? 'pending_reversal_release'
685
+ : book.available ? 'not_evaluated' : 'unavailable',
686
+ reasons: alreadyExecuted
687
+ ? ['already_executed_this_event', 'already_executed_durable_history']
688
+ : book.reasons,
689
+ },
690
+ authorization: authorization
691
+ ? { ...authorization, executionSymbol: executionSymbol(candidate.symbol) }
692
+ : null,
693
+ };
694
+ });
695
+ const reversalDue = position
696
+ ? (position.side === 'long' ? daily.exitLong : daily.exitShort)
697
+ : false;
698
+ return {
699
+ symbol,
700
+ currentReturn: daily.currentReturn,
701
+ previousReturn: daily.previousReturn,
702
+ atr14: daily.atr14,
703
+ completedDailyReferenceClose: daily.referencePrice,
704
+ entryTransition: entries.length > 0,
705
+ capDecision: entries.length > 0
706
+ ? 'see_entries'
707
+ : { status: 'not_candidate', reason: 'no_completed_daily_zero_cross' },
708
+ entries,
709
+ reversal: {
710
+ exitLong: daily.exitLong,
711
+ exitShort: daily.exitShort,
712
+ observedPositionMissionId: position?.missionId ?? null,
713
+ observedPositionSide: position?.side ?? null,
714
+ due: reversalDue,
715
+ timing: reversalDue && entryWindowStatus === 'missed_open'
716
+ ? 'missed_open_late_exit'
717
+ : reversalDue ? 'at_open_window' : 'not_due',
718
+ executionSymbol: position ? executionSymbol(symbol) : null,
719
+ authorization: position?.missionId && exitAuthorizationByMission.has(position.missionId)
720
+ ? {
721
+ ...exitAuthorizationByMission.get(position.missionId),
722
+ executionSymbol: executionSymbol(symbol),
723
+ }
724
+ : null,
725
+ },
726
+ };
727
+ });
728
+ const allCandidatesAlreadyExecuted = candidates.length > 0
729
+ && candidates.every((candidate) => alreadyExecutedThisEvent.has(candidate.id));
730
+ const allCandidateSymbolsOccupied = candidates.length > 0
731
+ && candidates.every((candidate) => occupiedNonWave9Symbols.has(candidate.symbol));
732
+ const entryAuthorizationReason = issuedEntries.length > 0
733
+ ? 'authorized'
734
+ : lockedState.state !== 'active'
735
+ ? 'strategy_not_active_for_entries'
736
+ : pendingReversalRelease
737
+ ? 'execute_reversal_then_recall_status'
738
+ : entryWindowStatus === 'missed_open'
739
+ ? 'entry_window_closed'
740
+ : !book.available
741
+ ? 'account_admission_unavailable'
742
+ : candidates.length === 0
743
+ ? 'no_entry_signal'
744
+ : admissionCandidates.length === 0
745
+ ? allCandidatesAlreadyExecuted
746
+ ? 'all_entry_signals_already_executed'
747
+ : allCandidateSymbolsOccupied
748
+ ? 'all_entry_signal_symbols_occupied_by_non_wave9_positions'
749
+ : 'entry_signals_not_eligible_for_admission'
750
+ : admissionDecisions.some((decision) => decision.accepted)
751
+ ? 'authorization_unavailable'
752
+ : 'no_candidate_passed_admission';
753
+ const exitAuthorizationReason = issuedExits.length > 0
754
+ ? 'authorized'
755
+ : lockedWave9Positions.length === 0
756
+ ? 'no_managed_position'
757
+ : reversalDuePositions.length === 0
758
+ ? 'no_reversal_signal'
759
+ : 'reversal_authorization_unavailable';
760
+ return {
761
+ ...statusWithCurrentAction(lockedBase, currentActionAuthorization(issuedEntries.length, issuedExits.length, entryAuthorizationReason, exitAuthorizationReason, authorizationAbsenceMeaning(lockedState.state, isExecutableMode(snapshot.tradingMode), deps.executionVenue === 'binance'))),
762
+ eventTime,
763
+ evidenceAvailable: true,
764
+ evidence: {
765
+ venue: batch.venue,
766
+ marketType: batch.marketType,
767
+ atomicUniverse: true,
768
+ symbolOrder: [...WAVE9_SYMBOL_PRIORITY],
769
+ },
770
+ entryWindow: {
771
+ status: entryWindowStatus,
772
+ opensAt: eventTime,
773
+ closesAt: new Date(entryDeadlineMs).toISOString(),
774
+ halfOpen: true,
775
+ observedDelayMs: nowMs - eventMs,
776
+ lateEntryPolicy: 'do_not_chase',
777
+ },
778
+ accountAdmission: {
779
+ available: book.available
780
+ && lockedState.state === 'active'
781
+ && !pendingReversalRelease,
782
+ source: snapshot.source,
783
+ cashEquityUsd: snapshot.cashEquityUsd ?? null,
784
+ reasons: book.reasons,
785
+ limitations: book.limitations,
786
+ },
787
+ decisions,
788
+ authorizations: {
789
+ entries: issuedEntries,
790
+ exits: issuedExits,
791
+ supportedModes: ['PAPER', 'LIVE'],
792
+ orderMutationPerformed: false,
793
+ },
794
+ };
795
+ });
796
+ }