@reefclaw/openclaw-plugin 0.1.5 → 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.
- package/ccxt/binance-private.d.ts +21 -0
- package/ccxt/binance-private.js +132 -22
- package/config/plugin-config-io.d.ts +6 -0
- package/config/tool-gate.js +3 -0
- package/exchange-adapter.d.ts +16 -0
- package/index.js +551 -54
- package/lifecycle/trading-operation-lock.d.ts +17 -0
- package/lifecycle/trading-operation-lock.js +14 -0
- package/live/bracket-id.d.ts +2 -3
- package/live/bracket-id.js +22 -9
- package/live/live-adapter.d.ts +24 -1
- package/live/live-adapter.js +114 -2
- package/live/local-signal-service.js +11 -6
- package/live/local-strategy-evaluator.js +4 -0
- package/live/proposal-decision-listener.d.ts +6 -0
- package/live/proposal-decision-listener.js +4 -0
- package/live/stop-watcher.d.ts +27 -1
- package/live/stop-watcher.js +59 -2
- package/onboarding/runtime.d.ts +13 -0
- package/onboarding/runtime.js +22 -2
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/portfolio/wave9-admission.d.ts +67 -0
- package/portfolio/wave9-admission.js +262 -0
- package/portfolio/wave9-policy.d.ts +36 -0
- package/portfolio/wave9-policy.js +183 -0
- package/signals/conditions/registry.js +50 -0
- package/simulator/exchange-simulator.js +5 -0
- package/simulator/fill-engine.js +5 -1
- package/simulator/types.d.ts +6 -1
- package/strategy/evaluator.d.ts +3 -0
- package/strategy/evaluator.js +5 -0
- package/tools/assessment-validation.d.ts +2 -0
- package/tools/attach-brackets.d.ts +7 -2
- package/tools/attach-brackets.js +36 -0
- package/tools/cancel-all-orders.d.ts +2 -0
- package/tools/cancel-all-orders.js +4 -1
- package/tools/cancel-order.d.ts +4 -0
- package/tools/cancel-order.js +49 -3
- package/tools/close-position.d.ts +23 -0
- package/tools/close-position.js +286 -13
- package/tools/create-order.d.ts +28 -0
- package/tools/create-order.js +1364 -192
- package/tools/get-analytics.js +2 -2
- package/tools/get-basis.js +2 -2
- package/tools/get-cascade-risk.js +2 -2
- package/tools/get-crypto-metrics.js +14 -4
- package/tools/get-cvd.js +2 -2
- package/tools/get-divergences.js +2 -2
- package/tools/get-funding-context.js +2 -2
- package/tools/get-liquidation-levels.js +2 -2
- package/tools/get-liquidation-pulse.js +2 -2
- package/tools/get-pattern-scan.js +2 -2
- package/tools/get-regime.js +2 -2
- package/tools/get-resting-liquidity.js +2 -2
- package/tools/get-risk-scenario.js +2 -2
- package/tools/get-session-review.js +2 -2
- package/tools/get-setup-detail.js +9 -1
- package/tools/get-signals.js +2 -2
- package/tools/get-sizing.js +2 -2
- package/tools/get-trade-feedback.js +2 -2
- package/tools/get-trade-flow.js +2 -2
- package/tools/get-volume-profile.js +2 -2
- package/tools/get-wave9-status.d.ts +127 -0
- package/tools/get-wave9-status.js +796 -0
- package/tools/intel-api.d.ts +20 -0
- package/tools/intel-api.js +67 -0
- package/tools/intel-cache.d.ts +1 -1
- package/tools/intel-cache.js +20 -5
- package/tools/list-strategies.d.ts +11 -1
- package/tools/list-strategies.js +17 -0
- package/tools/modify-stop.d.ts +4 -0
- package/tools/modify-stop.js +63 -24
- package/tools/modify-target.d.ts +4 -0
- package/tools/modify-target.js +62 -23
- package/tools/scan-pairs.js +19 -8
- package/tools/toggle-strategy.js +7 -0
- package/types.d.ts +5 -0
- package/venues/hyperliquid/hl-balance.d.ts +116 -0
- package/venues/hyperliquid/hl-balance.js +145 -0
- package/venues/hyperliquid/hl-brackets.d.ts +102 -0
- package/venues/hyperliquid/hl-brackets.js +172 -0
- package/venues/hyperliquid/hl-cloid.d.ts +22 -0
- package/venues/hyperliquid/hl-cloid.js +82 -0
- package/venues/hyperliquid/hl-info-cache.d.ts +46 -0
- package/venues/hyperliquid/hl-info-cache.js +125 -0
- package/venues/hyperliquid/hl-live-adapter.d.ts +88 -0
- package/venues/hyperliquid/hl-live-adapter.js +353 -0
- package/venues/hyperliquid/hl-precision.d.ts +61 -0
- package/venues/hyperliquid/hl-precision.js +176 -0
- package/venues/hyperliquid/hl-private.d.ts +88 -0
- package/venues/hyperliquid/hl-private.js +357 -0
- package/venues/hyperliquid/hl-public.d.ts +31 -4
- package/venues/hyperliquid/hl-public.js +155 -11
- package/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
- package/venues/hyperliquid/hl-rate-gate.js +220 -0
- package/venues/hyperliquid/hl-user-stream.d.ts +90 -0
- package/venues/hyperliquid/hl-user-stream.js +220 -0
- package/venues/registry.d.ts +23 -9
- package/venues/registry.js +12 -13
- package/venues/symbols.d.ts +43 -0
- package/venues/symbols.js +107 -0
- package/wave9/live-account-capture.d.ts +67 -0
- package/wave9/live-account-capture.js +435 -0
- package/wave9/live-autonomous-protection.d.ts +39 -0
- package/wave9/live-autonomous-protection.js +112 -0
- package/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
- package/wave9/live-durable-reconciliation-scheduler.js +115 -0
- package/wave9/live-execution-ledger.d.ts +107 -0
- package/wave9/live-execution-ledger.js +498 -0
- package/wave9/live-position-confirmation.d.ts +18 -0
- package/wave9/live-position-confirmation.js +111 -0
- package/wave9/live-residual-protection.d.ts +18 -0
- package/wave9/live-residual-protection.js +250 -0
- package/wave9/live-startup-reconciliation.d.ts +38 -0
- package/wave9/live-startup-reconciliation.js +454 -0
- package/wave9/live-symbol-ownership.d.ts +20 -0
- package/wave9/live-symbol-ownership.js +132 -0
- package/wave9/paper-admission-guard.d.ts +199 -0
- package/wave9/paper-admission-guard.js +650 -0
- package/wave9/usdm-evidence-provider.d.ts +42 -0
- package/wave9/usdm-evidence-provider.js +133 -0
|
@@ -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
|
+
}
|