@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.
- 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 +567 -57
- 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/persistence/state-manager.d.ts +24 -0
- package/persistence/state-manager.js +62 -4
- 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 +20 -3
- 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 +28 -9
- package/venues/registry.js +19 -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,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow process-local serialization boundary shared by guarded trading
|
|
3
|
+
* operations. Account and per-symbol operations intentionally use one queue:
|
|
4
|
+
* a Wave 9 portfolio transition must be atomic with respect to the legacy
|
|
5
|
+
* stop watcher, while callers gain no adapter/execution capability from the
|
|
6
|
+
* lock itself.
|
|
7
|
+
*/
|
|
8
|
+
export interface TradingOperationLock {
|
|
9
|
+
withAccountLock<T>(operation: string, run: () => Promise<T>): Promise<T>;
|
|
10
|
+
withSymbolLock<T>(symbol: string, operation: string, run: () => Promise<T>): Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
export declare class SerialTradingOperationLock implements TradingOperationLock {
|
|
13
|
+
private tail;
|
|
14
|
+
private enqueue;
|
|
15
|
+
withAccountLock<T>(_operation: string, run: () => Promise<T>): Promise<T>;
|
|
16
|
+
withSymbolLock<T>(_symbol: string, _operation: string, run: () => Promise<T>): Promise<T>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class SerialTradingOperationLock {
|
|
2
|
+
tail = Promise.resolve();
|
|
3
|
+
enqueue(run) {
|
|
4
|
+
const result = this.tail.then(run, run);
|
|
5
|
+
this.tail = result.then(() => undefined, () => undefined);
|
|
6
|
+
return result;
|
|
7
|
+
}
|
|
8
|
+
withAccountLock(_operation, run) {
|
|
9
|
+
return this.enqueue(run);
|
|
10
|
+
}
|
|
11
|
+
withSymbolLock(_symbol, _operation, run) {
|
|
12
|
+
return this.enqueue(run);
|
|
13
|
+
}
|
|
14
|
+
}
|
package/live/bracket-id.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { VenueId } from '@reefclaw/shared';
|
|
2
1
|
import type { BracketId, BracketRole } from './bracket-types.js';
|
|
2
|
+
import type { VenueId } from '../venues/symbols.js';
|
|
3
3
|
/** Generate a fresh 16-char hex bracketId. */
|
|
4
4
|
export declare function generateBracketId(): BracketId;
|
|
5
5
|
/** Build a clientOrderId for a given bracket + role. Emits the CURRENT
|
|
@@ -17,8 +17,7 @@ export declare function parseBracketCid(cid: string): {
|
|
|
17
17
|
/** Cheap type-guard for "is this a reefclaw-managed bracket clientOrderId"
|
|
18
18
|
* — true for both the current and legacy schemes. */
|
|
19
19
|
export declare function isBracketCid(cid: string): boolean;
|
|
20
|
-
/** Venue-aware parseBracketCid
|
|
21
|
-
* parser; other venues return null until their scheme ships. */
|
|
20
|
+
/** Venue-aware parseBracketCid — Binance's dual scheme, Hyperliquid's cloids. */
|
|
22
21
|
export declare function parseBracketClientId(venue: VenueId, cid: string): {
|
|
23
22
|
bracketId: BracketId;
|
|
24
23
|
role: BracketRole;
|
package/live/bracket-id.js
CHANGED
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
// alphanumeric) is well within it. Verified vs developers.binance.com
|
|
36
36
|
// USDⓂ New-Order clientOrderId filter, 2026-05-16.
|
|
37
37
|
import { randomBytes } from 'node:crypto';
|
|
38
|
+
import { parseHlBracketCloid, isHlBracketCloid } from '../venues/hyperliquid/hl-cloid.js';
|
|
38
39
|
// Emitted scheme (no `rc-`, no dashes).
|
|
39
40
|
const CID_REGEX = /^bkt([0-9a-f]{16})([est])$/;
|
|
40
41
|
// Legacy scheme — still parsed for backward compat, never emitted again.
|
|
@@ -83,17 +84,29 @@ export function isBracketCid(cid) {
|
|
|
83
84
|
//
|
|
84
85
|
// ALL bracket client-order-id recognition stays centralized in THIS module —
|
|
85
86
|
// the brackets.md rule ("never hardcode an rc-/bkt regex elsewhere") extends
|
|
86
|
-
// per-venue. Binance = the bkt/rc- schemes above. Hyperliquid
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
// per-venue. Binance = the bkt/rc- schemes above. Hyperliquid = 128-bit hex
|
|
88
|
+
// cloids (`0xbc7…`, hl-cloid.ts §5.7 — a Binance-style ASCII cid is not even a
|
|
89
|
+
// legal HL client id). Phase 3 wires that dispatch HERE, and nowhere else.
|
|
90
|
+
//
|
|
91
|
+
// ★ NEITHER VENUE MAY EVER RECOGNISE THE OTHER'S SCHEME. A false positive is
|
|
92
|
+
// destructive: `cancelAllOrders` preserves anything that parses as "ours", and
|
|
93
|
+
// the reconciler cancels/re-attaches on that basis. A foreign id that parsed as
|
|
94
|
+
// ours would be treated as a protective leg we own.
|
|
95
|
+
/** Venue-aware parseBracketCid — Binance's dual scheme, Hyperliquid's cloids. */
|
|
93
96
|
export function parseBracketClientId(venue, cid) {
|
|
94
|
-
|
|
97
|
+
switch (venue) {
|
|
98
|
+
case 'binance':
|
|
99
|
+
return parseBracketCid(cid);
|
|
100
|
+
case 'hyperliquid':
|
|
101
|
+
return parseHlBracketCloid(cid);
|
|
102
|
+
}
|
|
95
103
|
}
|
|
96
104
|
/** Venue-aware isBracketCid. */
|
|
97
105
|
export function isBracketClientId(venue, cid) {
|
|
98
|
-
|
|
106
|
+
switch (venue) {
|
|
107
|
+
case 'binance':
|
|
108
|
+
return isBracketCid(cid);
|
|
109
|
+
case 'hyperliquid':
|
|
110
|
+
return isHlBracketCloid(cid);
|
|
111
|
+
}
|
|
99
112
|
}
|
package/live/live-adapter.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import type { IExchangeAdapter, AdapterReadiness, OrderOptions } from '../exchange-adapter.js';
|
|
2
|
+
import type { IExchangeAdapter, AdapterReadiness, ClientOrderResolution, OrderOptions } from '../exchange-adapter.js';
|
|
3
3
|
import { BinancePrivateApi } from '../ccxt/binance-private.js';
|
|
4
|
+
import type { BinanceAlgoOrderProof } from '../ccxt/binance-private.js';
|
|
4
5
|
import type { CcxtOrder, CcxtBalance, CcxtPosition, TradingMode, ExchangeConfig } from '../types.js';
|
|
5
6
|
import type { PositionMetadata, CloseReason } from '../simulator/types.js';
|
|
6
7
|
import { ExchangeInfoCache } from './exchange-info-cache.js';
|
|
@@ -27,6 +28,16 @@ export interface TradeIngestWiring {
|
|
|
27
28
|
userId: string;
|
|
28
29
|
exchange?: string;
|
|
29
30
|
}
|
|
31
|
+
export interface Wave9ProtectionLossEvent {
|
|
32
|
+
source: 'algo_canceled' | 'algo_rejected' | 'reconciler_missing';
|
|
33
|
+
symbol: string;
|
|
34
|
+
bracketId: string;
|
|
35
|
+
entryClientOrderId: string;
|
|
36
|
+
entrySide: 'buy' | 'sell';
|
|
37
|
+
stopClientOrderId?: string;
|
|
38
|
+
detail: string;
|
|
39
|
+
}
|
|
40
|
+
export type Wave9ProtectionLossHandler = (event: Wave9ProtectionLossEvent) => Promise<void> | void;
|
|
30
41
|
/**
|
|
31
42
|
* Backfill `balance.free[cur] = total[cur]` for currencies where Binance's CCXT
|
|
32
43
|
* response left the per-currency `free` and `used` at zero despite a non-zero
|
|
@@ -55,6 +66,10 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
|
|
|
55
66
|
private bracketLedger;
|
|
56
67
|
private bracketManager;
|
|
57
68
|
private bracketReconciler;
|
|
69
|
+
private wave9ProtectionLossHandler?;
|
|
70
|
+
private readonly wave9ProtectionLossInFlight;
|
|
71
|
+
private readonly pendingWave9ProtectionLossBySymbol;
|
|
72
|
+
private static readonly PENDING_WAVE9_PROTECTION_LOSS_CAP;
|
|
58
73
|
private readonly userDataStreamMode;
|
|
59
74
|
private userDataStream;
|
|
60
75
|
private readonly wsAlgoStatusByCid;
|
|
@@ -128,6 +143,11 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
|
|
|
128
143
|
* the subsequent flatten.
|
|
129
144
|
*/
|
|
130
145
|
private attachBracketsAsync;
|
|
146
|
+
/** Install the Wave 9-only autonomous recovery bridge. With no handler,
|
|
147
|
+
* every generic bracket lifecycle remains byte-for-byte behaviorally
|
|
148
|
+
* unchanged. Runtime reapplies this setter after adapter reconnects. */
|
|
149
|
+
setWave9ProtectionLossHandler(handler?: Wave9ProtectionLossHandler): void;
|
|
150
|
+
private notifyWave9ProtectionLoss;
|
|
131
151
|
/**
|
|
132
152
|
* AUTHORITATIVE bracket-lifecycle handler — Binance's ALGO_UPDATE push.
|
|
133
153
|
*
|
|
@@ -194,6 +214,8 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
|
|
|
194
214
|
* fired (TRIGGERED/FINISHED) — only this licenses repair.
|
|
195
215
|
* 'unknown' = could not get an answer — safe-wait. */
|
|
196
216
|
resolveBracketLegLiveness(cid: string): Promise<'live' | 'terminal' | 'unknown'>;
|
|
217
|
+
/** Immediate full-shape proof for one exact native conditional order. */
|
|
218
|
+
resolveBracketLegProof(cid: string): Promise<BinanceAlgoOrderProof | null>;
|
|
197
219
|
/** Symbol-scoped generalization of the trusted-WS fast-path — the
|
|
198
220
|
* authoritative action-layer guard for the 2026-05-16 INJ bracket spiral.
|
|
199
221
|
*
|
|
@@ -256,6 +278,7 @@ export declare class LiveAdapter extends EventEmitter implements IExchangeAdapte
|
|
|
256
278
|
private decoratePositionsWithMetadata;
|
|
257
279
|
getOpenOrders(symbol?: string): Promise<CcxtOrder[]>;
|
|
258
280
|
fetchOrder(orderId: string, symbol?: string): Promise<CcxtOrder | null>;
|
|
281
|
+
resolveOrderByClientId(clientOrderId: string, symbol: string): Promise<ClientOrderResolution>;
|
|
259
282
|
getLastPrice(symbol: string): Promise<number | null>;
|
|
260
283
|
/** Sync rate limiter from exchange response headers after every private API call. */
|
|
261
284
|
private syncRateLimits;
|
package/live/live-adapter.js
CHANGED
|
@@ -122,6 +122,10 @@ export class LiveAdapter extends EventEmitter {
|
|
|
122
122
|
bracketLedger = null;
|
|
123
123
|
bracketManager = null;
|
|
124
124
|
bracketReconciler = null;
|
|
125
|
+
wave9ProtectionLossHandler;
|
|
126
|
+
wave9ProtectionLossInFlight = new Set();
|
|
127
|
+
pendingWave9ProtectionLossBySymbol = new Map();
|
|
128
|
+
static PENDING_WAVE9_PROTECTION_LOSS_CAP = 100;
|
|
125
129
|
// User-data WebSocket stream — null when mode='off' (REST polling owns reads).
|
|
126
130
|
// In Phase 1 this is dead code on prod (mode='off' is the default); the wiring
|
|
127
131
|
// is in place so Phase 2 onwards only requires a flag flip. Read paths
|
|
@@ -216,7 +220,26 @@ export class LiveAdapter extends EventEmitter {
|
|
|
216
220
|
this.bracketManager = new BracketManager(liveBracketApi, this.bracketLedger);
|
|
217
221
|
this.bracketManager.on('event', (ev) => this.emit('bracket_event', ev));
|
|
218
222
|
this.bracketReconciler = new BracketReconciler(this, this.bracketLedger, this.bracketManager, liveBracketApi);
|
|
219
|
-
this.bracketReconciler.on('drift', (d) =>
|
|
223
|
+
this.bracketReconciler.on('drift', (d) => {
|
|
224
|
+
this.emit('bracket_drift', d);
|
|
225
|
+
if (d.kind !== 'position_unprotected'
|
|
226
|
+
|| !d.symbol
|
|
227
|
+
|| !d.bracketId
|
|
228
|
+
|| !d.details?.split(/[+:\s]+/).includes('stop'))
|
|
229
|
+
return;
|
|
230
|
+
const row = this.bracketLedger?.getBySymbol(d.symbol);
|
|
231
|
+
if (!row || row.bracketId !== d.bracketId)
|
|
232
|
+
return;
|
|
233
|
+
this.notifyWave9ProtectionLoss({
|
|
234
|
+
source: 'reconciler_missing',
|
|
235
|
+
symbol: normalizeBracketSymbol(d.symbol),
|
|
236
|
+
bracketId: d.bracketId,
|
|
237
|
+
entryClientOrderId: row.entryCid,
|
|
238
|
+
entrySide: row.entrySide,
|
|
239
|
+
...(row.slCid ? { stopClientOrderId: row.slCid } : {}),
|
|
240
|
+
detail: d.details,
|
|
241
|
+
});
|
|
242
|
+
});
|
|
220
243
|
logger.info(TAG, `Bracket orders ENABLED in mode=${this.bracketMode}`);
|
|
221
244
|
}
|
|
222
245
|
// Forward poller events
|
|
@@ -814,7 +837,7 @@ export class LiveAdapter extends EventEmitter {
|
|
|
814
837
|
}
|
|
815
838
|
}
|
|
816
839
|
const bracketId = generateBracketId();
|
|
817
|
-
this.bracketManager.registerEntry({ symbol, side, stopPrice: metadata.stopPrice, targetPrice: metadata.targetPrice }, bracketId,
|
|
840
|
+
this.bracketManager.registerEntry({ symbol, side, stopPrice: metadata.stopPrice, targetPrice: metadata.targetPrice }, bracketId, clientOrderId);
|
|
818
841
|
if (order.status === 'closed' && order.filled > 0) {
|
|
819
842
|
// Market order (or limit-that-filled-instantly) — attach now.
|
|
820
843
|
// We do NOT await: attach can take seconds and we don't want to block
|
|
@@ -857,6 +880,56 @@ export class LiveAdapter extends EventEmitter {
|
|
|
857
880
|
logger.error(TAG, `attachBracketsAsync unexpected error for ${symbol}: ${formatError(err)}`);
|
|
858
881
|
}
|
|
859
882
|
}
|
|
883
|
+
/** Install the Wave 9-only autonomous recovery bridge. With no handler,
|
|
884
|
+
* every generic bracket lifecycle remains byte-for-byte behaviorally
|
|
885
|
+
* unchanged. Runtime reapplies this setter after adapter reconnects. */
|
|
886
|
+
setWave9ProtectionLossHandler(handler) {
|
|
887
|
+
this.wave9ProtectionLossHandler = handler;
|
|
888
|
+
if (!handler || this.pendingWave9ProtectionLossBySymbol.size === 0)
|
|
889
|
+
return;
|
|
890
|
+
const pending = [...this.pendingWave9ProtectionLossBySymbol.values()];
|
|
891
|
+
this.pendingWave9ProtectionLossBySymbol.clear();
|
|
892
|
+
for (const event of pending)
|
|
893
|
+
this.notifyWave9ProtectionLoss(event);
|
|
894
|
+
}
|
|
895
|
+
notifyWave9ProtectionLoss(event) {
|
|
896
|
+
const handler = this.wave9ProtectionLossHandler;
|
|
897
|
+
const key = normalizeBracketSymbol(event.symbol);
|
|
898
|
+
if (!handler || this.wave9ProtectionLossInFlight.has(key)) {
|
|
899
|
+
// initialize() may start the reconciler/user-data stream before the
|
|
900
|
+
// durable Wave 9 ledger is loaded by index.ts. Coalesce one latest loss
|
|
901
|
+
// per symbol and replay it when runtime installs the handler; never let
|
|
902
|
+
// that startup window silently discard a native-stop failure. The same
|
|
903
|
+
// queue catches a second loss while recovery is in flight — notably a
|
|
904
|
+
// freshly re-armed stop cancelled just before the first handler returns.
|
|
905
|
+
this.pendingWave9ProtectionLossBySymbol.set(key, event);
|
|
906
|
+
if (this.pendingWave9ProtectionLossBySymbol.size
|
|
907
|
+
> LiveAdapter.PENDING_WAVE9_PROTECTION_LOSS_CAP) {
|
|
908
|
+
const oldest = this.pendingWave9ProtectionLossBySymbol.keys().next().value;
|
|
909
|
+
if (oldest !== undefined) {
|
|
910
|
+
this.pendingWave9ProtectionLossBySymbol.delete(oldest);
|
|
911
|
+
logger.error(TAG, `Wave 9 protection-loss queue exceeded ${LiveAdapter.PENDING_WAVE9_PROTECTION_LOSS_CAP} ` +
|
|
912
|
+
`symbols and evicted ${oldest}; IMMEDIATE MANUAL INTERVENTION REQUIRED`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
this.wave9ProtectionLossInFlight.add(key);
|
|
918
|
+
void Promise.resolve()
|
|
919
|
+
.then(() => handler(event))
|
|
920
|
+
.catch((error) => {
|
|
921
|
+
logger.error(TAG, `Wave 9 protection-loss recovery failed for ${event.symbol}: ${formatError(error)}; ` +
|
|
922
|
+
'IMMEDIATE MANUAL INTERVENTION REQUIRED');
|
|
923
|
+
})
|
|
924
|
+
.finally(() => {
|
|
925
|
+
this.wave9ProtectionLossInFlight.delete(key);
|
|
926
|
+
const pending = this.pendingWave9ProtectionLossBySymbol.get(key);
|
|
927
|
+
if (pending) {
|
|
928
|
+
this.pendingWave9ProtectionLossBySymbol.delete(key);
|
|
929
|
+
this.notifyWave9ProtectionLoss(pending);
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
}
|
|
860
933
|
/**
|
|
861
934
|
* AUTHORITATIVE bracket-lifecycle handler — Binance's ALGO_UPDATE push.
|
|
862
935
|
*
|
|
@@ -896,6 +969,11 @@ export class LiveAdapter extends EventEmitter {
|
|
|
896
969
|
const symbol = normalizeBracketSymbol(ev.symbol);
|
|
897
970
|
const row = this.bracketLedger?.getBySymbol(symbol);
|
|
898
971
|
const status = ev.status.toUpperCase();
|
|
972
|
+
const rowIsRecoverable = !!row
|
|
973
|
+
&& (row.state === 'active' || row.state === 'partial' || row.state === 'attaching');
|
|
974
|
+
const isCurrentStop = parsed.role === 'stop'
|
|
975
|
+
&& row?.bracketId === parsed.bracketId
|
|
976
|
+
&& (!row.slCid || row.slCid === cid);
|
|
899
977
|
// Record the authoritative per-leg WS status for the Tier-1 fast-path of
|
|
900
978
|
// resolveBracketLegLiveness(). Every parsed rc-* lifecycle event, every
|
|
901
979
|
// branch — including CANCELED, which the ledger deliberately does NOT
|
|
@@ -989,12 +1067,34 @@ export class LiveAdapter extends EventEmitter {
|
|
|
989
1067
|
else {
|
|
990
1068
|
logger.info(TAG, `ALGO_UPDATE CANCELED on ${symbol} (${cid}) — ledger=${row?.state ?? 'none'} (benign: cancel of an already-terminal/absent bracket).`);
|
|
991
1069
|
}
|
|
1070
|
+
if (rowIsRecoverable && isCurrentStop && row) {
|
|
1071
|
+
this.notifyWave9ProtectionLoss({
|
|
1072
|
+
source: 'algo_canceled',
|
|
1073
|
+
symbol,
|
|
1074
|
+
bracketId: row.bracketId,
|
|
1075
|
+
entryClientOrderId: row.entryCid,
|
|
1076
|
+
entrySide: row.entrySide,
|
|
1077
|
+
stopClientOrderId: cid,
|
|
1078
|
+
detail: `exact native stop reported CANCELED (reason=${ev.reason ?? 'none'})`,
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
992
1081
|
return;
|
|
993
1082
|
}
|
|
994
1083
|
// NEW / TRIGGERING / REJECTED — informational. REJECTED is worth a WARN
|
|
995
1084
|
// (Binance refused the conditional order — e.g. margin check).
|
|
996
1085
|
if (status === 'REJECTED') {
|
|
997
1086
|
logger.warn(TAG, `ALGO_UPDATE REJECTED on ${symbol} (${cid}) reason=${ev.reason ?? 'none'} — bracket leg refused by Binance; position may be unprotected.`);
|
|
1087
|
+
if (rowIsRecoverable && isCurrentStop && row) {
|
|
1088
|
+
this.notifyWave9ProtectionLoss({
|
|
1089
|
+
source: 'algo_rejected',
|
|
1090
|
+
symbol,
|
|
1091
|
+
bracketId: row.bracketId,
|
|
1092
|
+
entryClientOrderId: row.entryCid,
|
|
1093
|
+
entrySide: row.entrySide,
|
|
1094
|
+
stopClientOrderId: cid,
|
|
1095
|
+
detail: `exact native stop reported REJECTED (reason=${ev.reason ?? 'none'})`,
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
998
1098
|
}
|
|
999
1099
|
}
|
|
1000
1100
|
/** Expose the bracket manager for agent-facing tools (modify_stop, etc.).
|
|
@@ -1062,6 +1162,12 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1062
1162
|
// Tier 3 — no answer anywhere.
|
|
1063
1163
|
return 'unknown';
|
|
1064
1164
|
}
|
|
1165
|
+
/** Immediate full-shape proof for one exact native conditional order. */
|
|
1166
|
+
async resolveBracketLegProof(cid) {
|
|
1167
|
+
if (!cid)
|
|
1168
|
+
return null;
|
|
1169
|
+
return this.api.queryAlgoOrderProof(cid);
|
|
1170
|
+
}
|
|
1065
1171
|
/** Symbol-scoped generalization of the trusted-WS fast-path — the
|
|
1066
1172
|
* authoritative action-layer guard for the 2026-05-16 INJ bracket spiral.
|
|
1067
1173
|
*
|
|
@@ -1534,6 +1640,9 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1534
1640
|
out.push({
|
|
1535
1641
|
...p,
|
|
1536
1642
|
setupType: p.setupType ?? freshMeta.setupType,
|
|
1643
|
+
missionId: p.missionId ?? freshMeta.missionId,
|
|
1644
|
+
candidateId: p.candidateId ?? freshMeta.candidateId,
|
|
1645
|
+
strategy: p.strategy ?? freshMeta.strategy,
|
|
1537
1646
|
thesis: p.thesis ?? freshMeta.thesis,
|
|
1538
1647
|
stopPrice: p.stopPrice ?? freshMeta.stopPrice,
|
|
1539
1648
|
targetPrice: p.targetPrice ?? freshMeta.targetPrice,
|
|
@@ -1618,6 +1727,9 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1618
1727
|
async fetchOrder(orderId, symbol) {
|
|
1619
1728
|
return this.api.fetchOrder(orderId, symbol);
|
|
1620
1729
|
}
|
|
1730
|
+
async resolveOrderByClientId(clientOrderId, symbol) {
|
|
1731
|
+
return this.api.resolveOrderByClientId(clientOrderId, symbol);
|
|
1732
|
+
}
|
|
1621
1733
|
async getLastPrice(symbol) {
|
|
1622
1734
|
const ticker = await this.api.fetchTicker(symbol);
|
|
1623
1735
|
return ticker?.last ?? null;
|
|
@@ -16,6 +16,9 @@ import { FactSubscriber } from './fact-subscriber.js';
|
|
|
16
16
|
import { LocalStrategyEvaluator, DEFAULT_EVALUATOR_PARAMS } from './local-strategy-evaluator.js';
|
|
17
17
|
const RECENT_RING = 50;
|
|
18
18
|
const DEFAULT_FACT_STALENESS_MS = 60_000;
|
|
19
|
+
function isDeclarativeConfig(config) {
|
|
20
|
+
return config.executionModel === undefined || config.executionModel === 'declarative';
|
|
21
|
+
}
|
|
19
22
|
export class LocalSignalService {
|
|
20
23
|
opts;
|
|
21
24
|
subscriber;
|
|
@@ -124,12 +127,14 @@ export class LocalSignalService {
|
|
|
124
127
|
if (!resp.ok)
|
|
125
128
|
return;
|
|
126
129
|
const body = (await resp.json());
|
|
127
|
-
|
|
130
|
+
if (!Array.isArray(body.strategies))
|
|
131
|
+
return;
|
|
132
|
+
const configs = body.strategies
|
|
128
133
|
.map((r) => r.config)
|
|
129
|
-
.filter((c) => !!c);
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
.filter((c) => !!c && isDeclarativeConfig(c));
|
|
135
|
+
// A valid empty list means all generic strategies are off (or only a
|
|
136
|
+
// dedicated portfolio bundle is active), so clear stale generic rows.
|
|
137
|
+
this.strategies = configs;
|
|
133
138
|
}
|
|
134
139
|
catch {
|
|
135
140
|
/* keep last-known strategies — fail-open */
|
|
@@ -137,7 +142,7 @@ export class LocalSignalService {
|
|
|
137
142
|
}
|
|
138
143
|
/** Test seam — set strategies without an HTTP fetch. */
|
|
139
144
|
__setStrategiesForTest(s) {
|
|
140
|
-
this.strategies = s;
|
|
145
|
+
this.strategies = s.filter((config) => isDeclarativeConfig(config));
|
|
141
146
|
}
|
|
142
147
|
/** Test seam — feed a raw SSE chunk to the subscriber. */
|
|
143
148
|
__ingestForTest(chunk) {
|
|
@@ -42,6 +42,10 @@ export class LocalStrategyEvaluator {
|
|
|
42
42
|
const states = [];
|
|
43
43
|
const signals = [];
|
|
44
44
|
for (const config of strategies) {
|
|
45
|
+
const executionModel = config.executionModel;
|
|
46
|
+
if (executionModel !== undefined && executionModel !== 'declarative') {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
45
49
|
const def = adaptStrategy(config);
|
|
46
50
|
// Regime gate (engine.ts): effective floor is the LOWER of the strategy's
|
|
47
51
|
// own minConfidence and the user's regimeConfidenceFloor.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { BinancePublicApi } from '../ccxt/binance-public.js';
|
|
2
2
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
3
3
|
import type { AutoCaptureContext } from '../ingest/position-auto-capture.js';
|
|
4
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
5
|
+
import type { Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
|
|
4
6
|
export interface ProposalDecisionListenerOptions {
|
|
5
7
|
baseUrl: string;
|
|
6
8
|
ingestToken: string;
|
|
@@ -8,6 +10,10 @@ export interface ProposalDecisionListenerOptions {
|
|
|
8
10
|
adapter: IExchangeAdapter;
|
|
9
11
|
binanceApi: BinancePublicApi;
|
|
10
12
|
autoCapture?: AutoCaptureContext;
|
|
13
|
+
/** Shared mutation serialization and durable Wave 9 ownership check for
|
|
14
|
+
* listener-side approved-order execution. */
|
|
15
|
+
operationLock?: TradingOperationLock;
|
|
16
|
+
checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
|
|
11
17
|
/** Active poll interval — used while there are pending proposals (or the
|
|
12
18
|
* previous tick fetched any). Default 3 s. Keeps approval-to-fire latency
|
|
13
19
|
* low when the operator is actively working through proposals. */
|
|
@@ -56,6 +56,8 @@ export class ProposalDecisionListener {
|
|
|
56
56
|
adapter: options.adapter,
|
|
57
57
|
binanceApi: options.binanceApi,
|
|
58
58
|
autoCapture: options.autoCapture,
|
|
59
|
+
operationLock: options.operationLock,
|
|
60
|
+
checkWave9LiveSymbolOwnership: options.checkWave9LiveSymbolOwnership,
|
|
59
61
|
pollIntervalMs: options.pollIntervalMs ?? 3_000,
|
|
60
62
|
pollIntervalIdleMs: options.pollIntervalIdleMs ?? 15_000,
|
|
61
63
|
driftAbandonR: options.driftAbandonR ?? 0.3,
|
|
@@ -221,6 +223,8 @@ export class ProposalDecisionListener {
|
|
|
221
223
|
binanceApi: this.opts.binanceApi,
|
|
222
224
|
adapter: this.opts.adapter,
|
|
223
225
|
autoCapture: this.opts.autoCapture,
|
|
226
|
+
operationLock: this.opts.operationLock,
|
|
227
|
+
checkWave9LiveSymbolOwnership: this.opts.checkWave9LiveSymbolOwnership,
|
|
224
228
|
// proposalManager + userId deliberately omitted — see above.
|
|
225
229
|
});
|
|
226
230
|
if ('error' in result) {
|
package/live/stop-watcher.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
3
3
|
import type { CcxtPosition } from '../types.js';
|
|
4
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
4
5
|
export declare const DEFAULT_INTERVAL_MS = 10000;
|
|
5
6
|
export interface StopTriggeredEvent {
|
|
6
7
|
symbol: string;
|
|
@@ -9,6 +10,26 @@ export interface StopTriggeredEvent {
|
|
|
9
10
|
markPrice: number;
|
|
10
11
|
quantity: number;
|
|
11
12
|
}
|
|
13
|
+
export type Wave9StopCloseOutcome = {
|
|
14
|
+
status: 'flat';
|
|
15
|
+
detail: string;
|
|
16
|
+
candidateId: string;
|
|
17
|
+
symbol: string;
|
|
18
|
+
} | {
|
|
19
|
+
status: 'protected' | 'manual_intervention';
|
|
20
|
+
detail: string;
|
|
21
|
+
candidateId: string;
|
|
22
|
+
symbol: string;
|
|
23
|
+
stopPrice: number;
|
|
24
|
+
residualQuantity?: number;
|
|
25
|
+
stopClientOrderId?: string;
|
|
26
|
+
};
|
|
27
|
+
/** Optional durable Wave 9 lifecycle. Generic watcher behavior is unchanged
|
|
28
|
+
* when this is absent or the current position has no owned candidate. */
|
|
29
|
+
export interface Wave9StopCloseLifecycle {
|
|
30
|
+
resolveCandidateId(position: CcxtPosition): Promise<string | undefined> | string | undefined;
|
|
31
|
+
settleAfterClose(candidateId: string, symbol: string): Promise<Wave9StopCloseOutcome>;
|
|
32
|
+
}
|
|
12
33
|
/** Decide whether a position has crossed its stop.
|
|
13
34
|
* Exported for direct unit-testing without spinning up a watcher loop. */
|
|
14
35
|
export declare function isStopBreached(position: CcxtPosition): boolean;
|
|
@@ -16,6 +37,8 @@ export declare class PositionWatcher extends EventEmitter {
|
|
|
16
37
|
private interval;
|
|
17
38
|
private intervalMs;
|
|
18
39
|
private adapter;
|
|
40
|
+
private readonly operationLock?;
|
|
41
|
+
private wave9CloseLifecycle?;
|
|
19
42
|
/** Symbols currently awaiting a close RPC — second trigger in the same tick
|
|
20
43
|
* is suppressed so the watcher cannot double-close. Cleared after the close
|
|
21
44
|
* call resolves (success or failure). */
|
|
@@ -23,7 +46,10 @@ export declare class PositionWatcher extends EventEmitter {
|
|
|
23
46
|
/** Symbols we've already logged a breach for this session, to avoid spamming
|
|
24
47
|
* the log every tick while the close is in flight. */
|
|
25
48
|
private notifiedBreach;
|
|
26
|
-
constructor(adapter: IExchangeAdapter, intervalMs?: number);
|
|
49
|
+
constructor(adapter: IExchangeAdapter, intervalMs?: number, operationLock?: TradingOperationLock);
|
|
50
|
+
/** Configure this after the durable execution ledger is ready. Runtime
|
|
51
|
+
* lifecycle hooks reapply it to every watcher after reconnect. */
|
|
52
|
+
setWave9CloseLifecycle(lifecycle?: Wave9StopCloseLifecycle): void;
|
|
27
53
|
start(): void;
|
|
28
54
|
stop(): void;
|
|
29
55
|
/** Run one check cycle. Exposed for tests (skip setInterval). */
|
package/live/stop-watcher.js
CHANGED
|
@@ -45,6 +45,8 @@ export class PositionWatcher extends EventEmitter {
|
|
|
45
45
|
interval = null;
|
|
46
46
|
intervalMs;
|
|
47
47
|
adapter;
|
|
48
|
+
operationLock;
|
|
49
|
+
wave9CloseLifecycle;
|
|
48
50
|
/** Symbols currently awaiting a close RPC — second trigger in the same tick
|
|
49
51
|
* is suppressed so the watcher cannot double-close. Cleared after the close
|
|
50
52
|
* call resolves (success or failure). */
|
|
@@ -52,10 +54,16 @@ export class PositionWatcher extends EventEmitter {
|
|
|
52
54
|
/** Symbols we've already logged a breach for this session, to avoid spamming
|
|
53
55
|
* the log every tick while the close is in flight. */
|
|
54
56
|
notifiedBreach = new Set();
|
|
55
|
-
constructor(adapter, intervalMs = DEFAULT_INTERVAL_MS) {
|
|
57
|
+
constructor(adapter, intervalMs = DEFAULT_INTERVAL_MS, operationLock) {
|
|
56
58
|
super();
|
|
57
59
|
this.adapter = adapter;
|
|
58
60
|
this.intervalMs = intervalMs;
|
|
61
|
+
this.operationLock = operationLock;
|
|
62
|
+
}
|
|
63
|
+
/** Configure this after the durable execution ledger is ready. Runtime
|
|
64
|
+
* lifecycle hooks reapply it to every watcher after reconnect. */
|
|
65
|
+
setWave9CloseLifecycle(lifecycle) {
|
|
66
|
+
this.wave9CloseLifecycle = lifecycle;
|
|
59
67
|
}
|
|
60
68
|
start() {
|
|
61
69
|
if (this.interval) {
|
|
@@ -130,8 +138,57 @@ export class PositionWatcher extends EventEmitter {
|
|
|
130
138
|
quantity: position.contracts,
|
|
131
139
|
};
|
|
132
140
|
this.emit('stop_triggered', event);
|
|
141
|
+
const close = async () => {
|
|
142
|
+
let trustedPosition = position;
|
|
143
|
+
// Re-read after acquiring the shared trading boundary. A Wave 9
|
|
144
|
+
// reversal or another stop may have flattened the symbol while this
|
|
145
|
+
// watcher was queued.
|
|
146
|
+
if (this.operationLock) {
|
|
147
|
+
const current = await this.adapter.getPositionsOrNull(position.symbol);
|
|
148
|
+
if (current === null) {
|
|
149
|
+
throw new Error('position recheck unavailable while holding trading operation lock');
|
|
150
|
+
}
|
|
151
|
+
const latest = current.find((candidate) => candidate.symbol === position.symbol);
|
|
152
|
+
if (!latest || !isStopBreached(latest)) {
|
|
153
|
+
logger.info(TAG, `${position.symbol} no longer stop-triggered after locked recheck — skipping`);
|
|
154
|
+
return 'skipped';
|
|
155
|
+
}
|
|
156
|
+
trustedPosition = latest;
|
|
157
|
+
}
|
|
158
|
+
let candidateId;
|
|
159
|
+
let ownershipError;
|
|
160
|
+
if (this.wave9CloseLifecycle) {
|
|
161
|
+
try {
|
|
162
|
+
candidateId = await this.wave9CloseLifecycle.resolveCandidateId(trustedPosition);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
ownershipError = formatError(error);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (ownershipError) {
|
|
169
|
+
throw new Error(`Wave 9 ownership resolution failed before stop close submission: ${ownershipError}; ` +
|
|
170
|
+
'IMMEDIATE MANUAL INTERVENTION REQUIRED');
|
|
171
|
+
}
|
|
172
|
+
await this.adapter.closePosition(trustedPosition.symbol, 'stop_watcher');
|
|
173
|
+
if (!candidateId || !this.wave9CloseLifecycle)
|
|
174
|
+
return 'closed';
|
|
175
|
+
const outcome = await this.wave9CloseLifecycle.settleAfterClose(candidateId, trustedPosition.symbol);
|
|
176
|
+
if (outcome.status === 'flat')
|
|
177
|
+
return 'closed';
|
|
178
|
+
if (outcome.status === 'protected') {
|
|
179
|
+
logger.warn(TAG, `Stop close left a Wave 9 residual on ${trustedPosition.symbol}; ` +
|
|
180
|
+
`${outcome.detail}. The exact frozen stop is proven and the watcher will retry.`);
|
|
181
|
+
this.emit('stop_residual_protected', { ...event, ...outcome });
|
|
182
|
+
return 'residual_protected';
|
|
183
|
+
}
|
|
184
|
+
throw new Error(outcome.detail);
|
|
185
|
+
};
|
|
133
186
|
try {
|
|
134
|
-
|
|
187
|
+
const result = this.operationLock
|
|
188
|
+
? await this.operationLock.withSymbolLock(position.symbol, `stop_watcher:${position.symbol}`, close)
|
|
189
|
+
: await close();
|
|
190
|
+
if (result !== 'closed')
|
|
191
|
+
return;
|
|
135
192
|
logger.info(TAG, `Auto-closed ${position.symbol} (reason=stop_watcher)`);
|
|
136
193
|
this.emit('stop_closed', event);
|
|
137
194
|
}
|
package/onboarding/runtime.d.ts
CHANGED
|
@@ -2,7 +2,9 @@ import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
|
2
2
|
import type { ExchangeConfig, TradingMode } from '../types.js';
|
|
3
3
|
import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
|
|
4
4
|
import type { PaperMarketFeed } from '../simulator/paper-market-feed.js';
|
|
5
|
+
import { LiveAdapter } from '../live/live-adapter.js';
|
|
5
6
|
import { PositionWatcher } from '../live/stop-watcher.js';
|
|
7
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
6
8
|
export interface MicroLiveConfig {
|
|
7
9
|
sizeCapPercent?: number;
|
|
8
10
|
maxPositionUSDT?: number;
|
|
@@ -13,6 +15,13 @@ export interface BuildAdapterInput {
|
|
|
13
15
|
microLive?: MicroLiveConfig;
|
|
14
16
|
simulator: ExchangeSimulator;
|
|
15
17
|
}
|
|
18
|
+
/** Wave 9 safety wiring is created only after its durable ledger is loaded.
|
|
19
|
+
* Runtime reapplies these hooks to both the bootstrap objects and every
|
|
20
|
+
* adapter/watcher built by a reconnect. */
|
|
21
|
+
export interface Wave9LiveLifecycleHooks {
|
|
22
|
+
configureLiveAdapter?(adapter: LiveAdapter): void;
|
|
23
|
+
configurePositionWatcher?(watcher: PositionWatcher, adapter: IExchangeAdapter): void;
|
|
24
|
+
}
|
|
16
25
|
/** Pure-ish factory: builds an adapter for the requested mode.
|
|
17
26
|
* Falls back to PaperAdapter if a live mode is requested without credentials —
|
|
18
27
|
* callers should pre-validate via `modeRequiresCredentials`, but this
|
|
@@ -36,6 +45,8 @@ export declare class PluginRuntime {
|
|
|
36
45
|
* stops fire and NAV/uPnL stay live for non-dashboard symbols). Lives for
|
|
37
46
|
* the runtime's lifetime; started in paper, stopped in live. */
|
|
38
47
|
private readonly _marketFeed;
|
|
48
|
+
private readonly operationLock?;
|
|
49
|
+
private wave9LiveLifecycleHooks?;
|
|
39
50
|
/** Reconnect is serialized — a second caller waits for the first to finish
|
|
40
51
|
* so we never tear down an adapter that's mid-rebuild. */
|
|
41
52
|
private reconnectInFlight;
|
|
@@ -45,11 +56,13 @@ export declare class PluginRuntime {
|
|
|
45
56
|
simulator: ExchangeSimulator;
|
|
46
57
|
stopWatcher?: PositionWatcher | null;
|
|
47
58
|
marketFeed?: PaperMarketFeed | null;
|
|
59
|
+
operationLock?: TradingOperationLock;
|
|
48
60
|
});
|
|
49
61
|
get adapter(): IExchangeAdapter;
|
|
50
62
|
get mode(): TradingMode;
|
|
51
63
|
get stopWatcher(): PositionWatcher | null;
|
|
52
64
|
get marketFeed(): PaperMarketFeed | null;
|
|
65
|
+
setWave9LiveLifecycleHooks(hooks?: Wave9LiveLifecycleHooks): void;
|
|
53
66
|
/**
|
|
54
67
|
* Swap the current adapter for a new one built from `next`. The old
|
|
55
68
|
* adapter's background loops are stopped before the new one is wired in.
|