@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.
- package/bridge/gateway/event-parser.d.ts +6 -1
- package/bridge/gateway/event-parser.js +19 -2
- package/bridge/gateway/poller.d.ts +1 -0
- package/bridge/gateway/poller.js +14 -2
- package/bridge/providers/gateway.d.ts +22 -2
- package/bridge/providers/gateway.js +67 -9
- package/ccxt/binance-private.d.ts +21 -0
- package/ccxt/binance-private.js +132 -22
- package/ccxt/public-market-data-api.d.ts +14 -0
- package/ccxt/public-market-data-api.js +15 -1
- package/config/plugin-config-io.d.ts +13 -0
- package/config/plugin-config-io.js +15 -0
- package/config/tool-gate.js +3 -0
- package/exchange-adapter.d.ts +16 -0
- package/index.js +658 -83
- package/ingest/position-auto-capture.d.ts +68 -0
- package/ingest/position-auto-capture.js +321 -23
- package/ingest/position-decisions-client.d.ts +7 -2
- package/ingest/position-decisions-client.js +13 -3
- package/ingest/reconcile-db-vs-exchange.d.ts +39 -1
- package/ingest/reconcile-db-vs-exchange.js +66 -10
- 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/fill-price.d.ts +13 -0
- package/live/fill-price.js +37 -0
- package/live/live-adapter.d.ts +57 -2
- package/live/live-adapter.js +290 -49
- package/live/local-signal-service.js +11 -6
- package/live/local-strategy-evaluator.js +4 -0
- package/live/position-state-store.d.ts +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 +35 -2
- package/live/stop-watcher.js +63 -3
- package/onboarding/runtime.d.ts +19 -0
- package/onboarding/runtime.js +34 -3
- package/openclaw.plugin.json +1 -0
- package/package.json +2 -2
- package/portfolio/reentry-tracker.d.ts +36 -0
- package/portfolio/reentry-tracker.js +127 -0
- 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 +61 -2
- package/signals/strategy-adapter.js +17 -7
- package/simulator/exchange-simulator.d.ts +12 -0
- package/simulator/exchange-simulator.js +78 -3
- package/simulator/fill-engine.js +5 -1
- package/simulator/types.d.ts +10 -1
- package/skills/reefclaw/SKILL.md +2 -0
- package/strategy/evaluator.d.ts +3 -0
- package/strategy/evaluator.js +5 -0
- package/tools/assessment-validation.d.ts +23 -0
- package/tools/assessment-validation.js +58 -0
- package/tools/attach-brackets.d.ts +7 -2
- package/tools/attach-brackets.js +201 -0
- package/tools/audit-bracket-protection.js +157 -1
- package/tools/bracket-control.d.ts +12 -0
- package/tools/bracket-control.js +35 -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 +1390 -190
- 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 +21 -2
- 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.d.ts +4 -0
- package/tools/scan-pairs.js +22 -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-bracket-coordinator.d.ts +123 -0
- package/venues/hyperliquid/hl-bracket-coordinator.js +533 -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 +146 -0
- package/venues/hyperliquid/hl-live-adapter.js +728 -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 +163 -12
- 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
package/live/live-adapter.js
CHANGED
|
@@ -22,9 +22,11 @@ import { BracketReconciler } from './bracket-reconciler.js';
|
|
|
22
22
|
import { LiveBracketApi } from './live-bracket-api.js';
|
|
23
23
|
import { generateBracketId, buildBracketCid, parseBracketCid } from './bracket-id.js';
|
|
24
24
|
import { validateStopDirection, validateTargetDirection } from './bracket-params.js';
|
|
25
|
+
import { fillPriceFromOrder } from './fill-price.js';
|
|
25
26
|
import { bracketsEnabled } from '../config/brackets-config.js';
|
|
26
27
|
import { userDataStreamEnabled, userDataStreamAuthoritative, DEFAULT_TUNABLES as USER_DATA_STREAM_DEFAULT_TUNABLES, } from '../config/user-data-stream-config.js';
|
|
27
28
|
import { UserDataStreamController } from './user-data-stream-controller.js';
|
|
29
|
+
import { onAutoFlattenClose } from '../ingest/position-auto-capture.js';
|
|
28
30
|
import { assertNotShuttingDown, registerOp } from '../lifecycle/shutdown-coordinator.js';
|
|
29
31
|
import { updateMfe } from '../mfe.js';
|
|
30
32
|
import { computeInvalidationHit } from '../pinned-plan.js';
|
|
@@ -122,6 +124,10 @@ export class LiveAdapter extends EventEmitter {
|
|
|
122
124
|
bracketLedger = null;
|
|
123
125
|
bracketManager = null;
|
|
124
126
|
bracketReconciler = null;
|
|
127
|
+
wave9ProtectionLossHandler;
|
|
128
|
+
wave9ProtectionLossInFlight = new Set();
|
|
129
|
+
pendingWave9ProtectionLossBySymbol = new Map();
|
|
130
|
+
static PENDING_WAVE9_PROTECTION_LOSS_CAP = 100;
|
|
125
131
|
// User-data WebSocket stream — null when mode='off' (REST polling owns reads).
|
|
126
132
|
// In Phase 1 this is dead code on prod (mode='off' is the default); the wiring
|
|
127
133
|
// is in place so Phase 2 onwards only requires a flag flip. Read paths
|
|
@@ -167,6 +173,11 @@ export class LiveAdapter extends EventEmitter {
|
|
|
167
173
|
// getPositions tick, dropped on closePosition or when a symbol vanishes from
|
|
168
174
|
// the open-positions snapshot.
|
|
169
175
|
liveMetadata = new Map();
|
|
176
|
+
// Position-decision auto-capture wiring (optional). Retained so the internal
|
|
177
|
+
// bracket_attach_failed auto-flatten can journal its close (issue #196): that
|
|
178
|
+
// flatten bypasses the close_position tool, so onClosePositionFilled never
|
|
179
|
+
// runs and the row would orphan as status='open'.
|
|
180
|
+
autoCapture;
|
|
170
181
|
// Readiness state — mutable, starts INIT_PENDING
|
|
171
182
|
_readiness = 'INIT_PENDING';
|
|
172
183
|
get readiness() {
|
|
@@ -190,6 +201,11 @@ export class LiveAdapter extends EventEmitter {
|
|
|
190
201
|
this.mode = mode;
|
|
191
202
|
this.bracketMode = bracketMode;
|
|
192
203
|
this.userDataStreamMode = userDataStreamMode;
|
|
204
|
+
// Retained for the auto-flatten journal close capture (issue #196): the
|
|
205
|
+
// adapter-internal `closePosition(…, 'bracket_attach_failed')` calls bypass
|
|
206
|
+
// the close_position tool, so onClosePositionFilled never runs for them —
|
|
207
|
+
// the row orphaned as status='open' until this capture existed.
|
|
208
|
+
this.autoCapture = autoCapture;
|
|
193
209
|
this.api = new BinancePrivateApi(config);
|
|
194
210
|
if (mode === 'MICRO_LIVE') {
|
|
195
211
|
// sizeCapMultiplier is 1.0 — the agent already sizes for the real wallet.
|
|
@@ -216,7 +232,26 @@ export class LiveAdapter extends EventEmitter {
|
|
|
216
232
|
this.bracketManager = new BracketManager(liveBracketApi, this.bracketLedger);
|
|
217
233
|
this.bracketManager.on('event', (ev) => this.emit('bracket_event', ev));
|
|
218
234
|
this.bracketReconciler = new BracketReconciler(this, this.bracketLedger, this.bracketManager, liveBracketApi);
|
|
219
|
-
this.bracketReconciler.on('drift', (d) =>
|
|
235
|
+
this.bracketReconciler.on('drift', (d) => {
|
|
236
|
+
this.emit('bracket_drift', d);
|
|
237
|
+
if (d.kind !== 'position_unprotected'
|
|
238
|
+
|| !d.symbol
|
|
239
|
+
|| !d.bracketId
|
|
240
|
+
|| !d.details?.split(/[+:\s]+/).includes('stop'))
|
|
241
|
+
return;
|
|
242
|
+
const row = this.bracketLedger?.getBySymbol(d.symbol);
|
|
243
|
+
if (!row || row.bracketId !== d.bracketId)
|
|
244
|
+
return;
|
|
245
|
+
this.notifyWave9ProtectionLoss({
|
|
246
|
+
source: 'reconciler_missing',
|
|
247
|
+
symbol: normalizeBracketSymbol(d.symbol),
|
|
248
|
+
bracketId: d.bracketId,
|
|
249
|
+
entryClientOrderId: row.entryCid,
|
|
250
|
+
entrySide: row.entrySide,
|
|
251
|
+
...(row.slCid ? { stopClientOrderId: row.slCid } : {}),
|
|
252
|
+
detail: d.details,
|
|
253
|
+
});
|
|
254
|
+
});
|
|
220
255
|
logger.info(TAG, `Bracket orders ENABLED in mode=${this.bracketMode}`);
|
|
221
256
|
}
|
|
222
257
|
// Forward poller events
|
|
@@ -719,6 +754,19 @@ export class LiveAdapter extends EventEmitter {
|
|
|
719
754
|
}
|
|
720
755
|
// ---- Post-submission bookkeeping ----
|
|
721
756
|
this.intentJournal.updateIntent(clientOrderId, { status: 'confirmed', exchangeOrderId: order.id });
|
|
757
|
+
// ---- Fill-price enrichment (issue #196) ----
|
|
758
|
+
// For a marketable LIMIT order Binance's synchronous RESULT response can
|
|
759
|
+
// come back avgPrice=0 even though it filled (RESULT only guarantees the
|
|
760
|
+
// fill for MARKET / special-timeInForce LIMIT orders). Every downstream
|
|
761
|
+
// consumer — bracket direction validation, slippage, journal entry capture
|
|
762
|
+
// — MUST key off the real fill, never the limit price. Re-query by
|
|
763
|
+
// clientOrderId (weight 1) to patch order.average BEFORE any of them read
|
|
764
|
+
// it. Market orders report avgPrice reliably, so this fires only on the
|
|
765
|
+
// filled-limit edge; best-effort — a failed re-query leaves order.average
|
|
766
|
+
// as-is (consumers then use a mark price / skip, never the limit).
|
|
767
|
+
if (order.status === 'closed' && order.filled > 0 && fillPriceFromOrder(order) === null) {
|
|
768
|
+
await this.enrichFilledAvgPrice(order, clientOrderId, symbol);
|
|
769
|
+
}
|
|
722
770
|
// Track slippage for market orders that filled immediately
|
|
723
771
|
if (order.status === 'closed' && order.average != null && expectedPrice > 0) {
|
|
724
772
|
this.slippageTracker.recordFill({
|
|
@@ -758,63 +806,84 @@ export class LiveAdapter extends EventEmitter {
|
|
|
758
806
|
// leg, register the bracket and (for market orders) attach immediately.
|
|
759
807
|
// Limit orders defer attach to the poller's 'filled' event handler.
|
|
760
808
|
if (this.bracketManager && this.bracketLedger && metadata && (metadata.stopPrice !== undefined || metadata.targetPrice !== undefined)) {
|
|
761
|
-
// Defense-in-depth
|
|
762
|
-
// errors against the ref price
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
//
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
809
|
+
// Defense-in-depth direction re-check. The pre-submission check above
|
|
810
|
+
// already rejected sign errors against the ref price; this re-checks once
|
|
811
|
+
// the order's outcome is known. Reference price by order state:
|
|
812
|
+
// - FILLED (status=closed, filled>0): the ACTUAL average fill — NEVER
|
|
813
|
+
// the limit price (issue #196). A marketable limit can fill percent-
|
|
814
|
+
// scale away from its limit, so comparing the bracket to the limit
|
|
815
|
+
// gives a FALSE invalid (needless auto-flatten — LTC 2026-07-14) or
|
|
816
|
+
// lets a genuinely wrong stop through. If the fill price is
|
|
817
|
+
// unresolvable, SKIP the check — the bracket attach (Binance -2021 on
|
|
818
|
+
// a wrong-side stop) is the arbiter; validating against the limit
|
|
819
|
+
// would resurrect the bug.
|
|
820
|
+
// - still-OPEN resting limit: no fill yet, so the limit price IS the
|
|
821
|
+
// correct reference (the order fills at/through its limit).
|
|
822
|
+
// A failure HERE is post-submission — clean up (flatten the filled
|
|
823
|
+
// position / cancel the resting order) BEFORE throwing.
|
|
824
|
+
let dirRef = null;
|
|
825
|
+
if (order.status === 'closed' && order.filled > 0) {
|
|
826
|
+
dirRef = fillPriceFromOrder(order);
|
|
827
|
+
if (dirRef === null) {
|
|
828
|
+
logger.warn(TAG, `Post-fill bracket direction check skipped for ${symbol}: actual fill price unresolvable (avg=${order.average ?? 'null'} cost=${order.cost} filled=${order.filled}); NOT validating against the limit price — the bracket attach (Binance -2021) is the arbiter`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
else if (order.status === 'open') {
|
|
832
|
+
const lim = order.price ?? price ?? 0;
|
|
833
|
+
dirRef = lim > 0 ? lim : null;
|
|
834
|
+
}
|
|
835
|
+
let dirMsg = null;
|
|
836
|
+
if (dirRef !== null && dirRef > 0) {
|
|
770
837
|
if (metadata.stopPrice !== undefined) {
|
|
771
|
-
dirMsg = validateStopDirection(side,
|
|
838
|
+
dirMsg = validateStopDirection(side, dirRef, metadata.stopPrice);
|
|
772
839
|
}
|
|
773
840
|
if (!dirMsg && metadata.targetPrice !== undefined) {
|
|
774
|
-
dirMsg = validateTargetDirection(side,
|
|
841
|
+
dirMsg = validateTargetDirection(side, dirRef, metadata.targetPrice);
|
|
775
842
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
}
|
|
793
|
-
catch (err) {
|
|
794
|
-
logger.warn(TAG, `Post-fill flatten attempt ${a}/4 for ${symbol} failed: ${formatError(err)}`);
|
|
795
|
-
if (a < 4)
|
|
796
|
-
await sleep(1500);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
if (!flattened) {
|
|
800
|
-
logger.error(TAG, `CRITICAL: post-fill flatten of ${symbol} FAILED after 4 attempts — position is NAKED, immediate operator intervention required`);
|
|
801
|
-
this.emit('emergency_progress', { action: 'flatten', status: 'failed', symbol, message: `Naked ${symbol} after invalid bracket — manual close required` });
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
else if (order.status === 'open') {
|
|
805
|
-
logger.error(TAG, `Post-submit bracket direction invalid for ${symbol} (${dirMsg}) — cancelling the resting entry order so it can't fill unprotected`);
|
|
843
|
+
}
|
|
844
|
+
if (dirMsg) {
|
|
845
|
+
if (order.status === 'closed' && order.filled > 0) {
|
|
846
|
+
logger.error(TAG, `Post-fill bracket direction invalid for ${symbol} (${dirMsg}, fill=${dirRef}) — flattening the just-filled position to avoid a naked entry`);
|
|
847
|
+
// We have HARD evidence the entry filled (status=closed, filled>0).
|
|
848
|
+
// closePosition throws "No open position" when its attempt-1
|
|
849
|
+
// fetchPositions returns a *confirmed-empty* snapshot — which, in
|
|
850
|
+
// the moments right after a fill, is almost always Binance
|
|
851
|
+
// position-state lagging the order response, NOT a real flat.
|
|
852
|
+
// Accepting that single failure would fail open and leave the
|
|
853
|
+
// position naked (the exact high-risk branch this cleanup exists
|
|
854
|
+
// for). Retry across the lag window instead of trusting one snapshot.
|
|
855
|
+
// closePosition journals the auto-flatten close itself (keyed on the
|
|
856
|
+
// 'bracket_attach_failed' reason) so the row can't orphan (#196).
|
|
857
|
+
let flattened = false;
|
|
858
|
+
for (let a = 1; a <= 4 && !flattened; a++) {
|
|
806
859
|
try {
|
|
807
|
-
await this.
|
|
860
|
+
await this.closePosition(symbol, 'bracket_attach_failed');
|
|
861
|
+
flattened = true;
|
|
808
862
|
}
|
|
809
863
|
catch (err) {
|
|
810
|
-
logger.
|
|
864
|
+
logger.warn(TAG, `Post-fill flatten attempt ${a}/4 for ${symbol} failed: ${formatError(err)}`);
|
|
865
|
+
if (a < 4)
|
|
866
|
+
await sleep(1500);
|
|
811
867
|
}
|
|
812
868
|
}
|
|
813
|
-
|
|
869
|
+
if (!flattened) {
|
|
870
|
+
logger.error(TAG, `CRITICAL: post-fill flatten of ${symbol} FAILED after 4 attempts — position is NAKED, immediate operator intervention required`);
|
|
871
|
+
this.emit('emergency_progress', { action: 'flatten', status: 'failed', symbol, message: `Naked ${symbol} after invalid bracket — manual close required` });
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
else if (order.status === 'open') {
|
|
875
|
+
logger.error(TAG, `Post-submit bracket direction invalid for ${symbol} (${dirMsg}) — cancelling the resting entry order so it can't fill unprotected`);
|
|
876
|
+
try {
|
|
877
|
+
await this.api.cancelOrderByClientId(clientOrderId, symbol);
|
|
878
|
+
}
|
|
879
|
+
catch (err) {
|
|
880
|
+
logger.error(TAG, `Cancel of resting entry after invalid bracket failed for ${symbol}: ${formatError(err)}`);
|
|
881
|
+
}
|
|
814
882
|
}
|
|
883
|
+
throw new Error(`Bracket rejected (post-submit): ${dirMsg}`);
|
|
815
884
|
}
|
|
816
885
|
const bracketId = generateBracketId();
|
|
817
|
-
this.bracketManager.registerEntry({ symbol, side, stopPrice: metadata.stopPrice, targetPrice: metadata.targetPrice }, bracketId,
|
|
886
|
+
this.bracketManager.registerEntry({ symbol, side, stopPrice: metadata.stopPrice, targetPrice: metadata.targetPrice }, bracketId, clientOrderId);
|
|
818
887
|
if (order.status === 'closed' && order.filled > 0) {
|
|
819
888
|
// Market order (or limit-that-filled-instantly) — attach now.
|
|
820
889
|
// We do NOT await: attach can take seconds and we don't want to block
|
|
@@ -843,7 +912,8 @@ export class LiveAdapter extends EventEmitter {
|
|
|
843
912
|
const result = await this.bracketManager.attachBrackets(symbol, entryAmount);
|
|
844
913
|
if (!result.ok) {
|
|
845
914
|
logger.error(TAG, `Bracket attach FAILED for ${symbol} after ${result.attempts} attempts: ${result.error}. Auto-flattening position.`);
|
|
846
|
-
// Best-effort emergency close. closePosition has its own retry logic
|
|
915
|
+
// Best-effort emergency close. closePosition has its own retry logic +
|
|
916
|
+
// journals the auto-flatten close itself (keyed on the reason, #196).
|
|
847
917
|
try {
|
|
848
918
|
await this.closePosition(symbol, 'bracket_attach_failed');
|
|
849
919
|
}
|
|
@@ -857,6 +927,114 @@ export class LiveAdapter extends EventEmitter {
|
|
|
857
927
|
logger.error(TAG, `attachBracketsAsync unexpected error for ${symbol}: ${formatError(err)}`);
|
|
858
928
|
}
|
|
859
929
|
}
|
|
930
|
+
/**
|
|
931
|
+
* Patch `order.average` with the authoritative average fill price when a
|
|
932
|
+
* filled order came back without one (marketable-limit RESULT quirk, #196).
|
|
933
|
+
* Re-queries by clientOrderId (GET /fapi/v1/order, weight 1). Best-effort:
|
|
934
|
+
* on any failure or an unresolvable re-query the order is left as-is —
|
|
935
|
+
* downstream then falls back to a mark price / skips capture, NEVER to the
|
|
936
|
+
* limit price. Mutates `order` in place.
|
|
937
|
+
*/
|
|
938
|
+
async enrichFilledAvgPrice(order, clientOrderId, symbol) {
|
|
939
|
+
const rawAvg = order.average;
|
|
940
|
+
try {
|
|
941
|
+
const requeried = await this.api.fetchOrderByClientId(clientOrderId, symbol);
|
|
942
|
+
const resolved = requeried ? fillPriceFromOrder(requeried) : null;
|
|
943
|
+
if (resolved !== null && resolved > 0) {
|
|
944
|
+
order.average = resolved;
|
|
945
|
+
// Carry cumQuote across too, so cost-based consumers (VWAP, slippage)
|
|
946
|
+
// see the settled notional rather than 0.
|
|
947
|
+
if ((order.cost === 0 || order.cost == null) && requeried && requeried.cost > 0) {
|
|
948
|
+
order.cost = requeried.cost;
|
|
949
|
+
}
|
|
950
|
+
logger.info(TAG, `Fill-price enriched for ${symbol}: avg ${rawAvg ?? 'null'} → ${resolved} (re-query by cid; limit price ${order.price ?? 'n/a'})`);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
logger.warn(TAG, `Fill-price enrichment for ${symbol} could not resolve an average (re-query avg=${requeried?.average ?? 'null'} cost=${requeried?.cost ?? 'null'} filled=${requeried?.filled ?? 'null'}); downstream uses mark price / skips capture, NOT the limit price ${order.price ?? 'n/a'}`);
|
|
954
|
+
}
|
|
955
|
+
catch (err) {
|
|
956
|
+
logger.warn(TAG, `Fill-price enrichment re-query for ${symbol} threw: ${formatError(err)}; leaving average as-is`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Journal the close produced by an internal auto-flatten
|
|
961
|
+
* (`bracket_attach_failed`), so the position row can't orphan as
|
|
962
|
+
* status='open' (issue #196). These flattens go straight to
|
|
963
|
+
* `this.closePosition`, bypassing the close_position tool, so
|
|
964
|
+
* `onClosePositionFilled` never runs for them. Fire-and-forget + fail-open —
|
|
965
|
+
* never blocks or throws into the flatten path. The entry journal may not be
|
|
966
|
+
* ready at flatten time (the entry's WS fill can land ~1-2s later), so
|
|
967
|
+
* `onAutoFlattenClose` polls briefly for the webappPositionId; the boot DB
|
|
968
|
+
* reconcile sweep is the backstop if the entry never journals.
|
|
969
|
+
*/
|
|
970
|
+
captureAutoFlattenClose(symbol, closeOrder) {
|
|
971
|
+
if (!this.autoCapture)
|
|
972
|
+
return;
|
|
973
|
+
const fillPrice = fillPriceFromOrder(closeOrder);
|
|
974
|
+
// A synthetic "already flat" return (filled=0, avg=null) means another path
|
|
975
|
+
// (a bracket fill / external close) owns the real close — don't mislabel it
|
|
976
|
+
// as bracket_attach_failed and don't invent a price.
|
|
977
|
+
if (fillPrice === null || !(closeOrder.filled > 0))
|
|
978
|
+
return;
|
|
979
|
+
void onAutoFlattenClose(this.autoCapture, {
|
|
980
|
+
symbol,
|
|
981
|
+
fillPrice,
|
|
982
|
+
fillSize: Math.abs(closeOrder.filled),
|
|
983
|
+
...(typeof closeOrder.id === 'string' && closeOrder.id.length > 0
|
|
984
|
+
? { exchangeTradeId: closeOrder.id }
|
|
985
|
+
: {}),
|
|
986
|
+
}).catch((err) => logger.warn(TAG, `auto-flatten close capture for ${symbol} threw: ${formatError(err)}`));
|
|
987
|
+
}
|
|
988
|
+
/** Install the Wave 9-only autonomous recovery bridge. With no handler,
|
|
989
|
+
* every generic bracket lifecycle remains byte-for-byte behaviorally
|
|
990
|
+
* unchanged. Runtime reapplies this setter after adapter reconnects. */
|
|
991
|
+
setWave9ProtectionLossHandler(handler) {
|
|
992
|
+
this.wave9ProtectionLossHandler = handler;
|
|
993
|
+
if (!handler || this.pendingWave9ProtectionLossBySymbol.size === 0)
|
|
994
|
+
return;
|
|
995
|
+
const pending = [...this.pendingWave9ProtectionLossBySymbol.values()];
|
|
996
|
+
this.pendingWave9ProtectionLossBySymbol.clear();
|
|
997
|
+
for (const event of pending)
|
|
998
|
+
this.notifyWave9ProtectionLoss(event);
|
|
999
|
+
}
|
|
1000
|
+
notifyWave9ProtectionLoss(event) {
|
|
1001
|
+
const handler = this.wave9ProtectionLossHandler;
|
|
1002
|
+
const key = normalizeBracketSymbol(event.symbol);
|
|
1003
|
+
if (!handler || this.wave9ProtectionLossInFlight.has(key)) {
|
|
1004
|
+
// initialize() may start the reconciler/user-data stream before the
|
|
1005
|
+
// durable Wave 9 ledger is loaded by index.ts. Coalesce one latest loss
|
|
1006
|
+
// per symbol and replay it when runtime installs the handler; never let
|
|
1007
|
+
// that startup window silently discard a native-stop failure. The same
|
|
1008
|
+
// queue catches a second loss while recovery is in flight — notably a
|
|
1009
|
+
// freshly re-armed stop cancelled just before the first handler returns.
|
|
1010
|
+
this.pendingWave9ProtectionLossBySymbol.set(key, event);
|
|
1011
|
+
if (this.pendingWave9ProtectionLossBySymbol.size
|
|
1012
|
+
> LiveAdapter.PENDING_WAVE9_PROTECTION_LOSS_CAP) {
|
|
1013
|
+
const oldest = this.pendingWave9ProtectionLossBySymbol.keys().next().value;
|
|
1014
|
+
if (oldest !== undefined) {
|
|
1015
|
+
this.pendingWave9ProtectionLossBySymbol.delete(oldest);
|
|
1016
|
+
logger.error(TAG, `Wave 9 protection-loss queue exceeded ${LiveAdapter.PENDING_WAVE9_PROTECTION_LOSS_CAP} ` +
|
|
1017
|
+
`symbols and evicted ${oldest}; IMMEDIATE MANUAL INTERVENTION REQUIRED`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
this.wave9ProtectionLossInFlight.add(key);
|
|
1023
|
+
void Promise.resolve()
|
|
1024
|
+
.then(() => handler(event))
|
|
1025
|
+
.catch((error) => {
|
|
1026
|
+
logger.error(TAG, `Wave 9 protection-loss recovery failed for ${event.symbol}: ${formatError(error)}; ` +
|
|
1027
|
+
'IMMEDIATE MANUAL INTERVENTION REQUIRED');
|
|
1028
|
+
})
|
|
1029
|
+
.finally(() => {
|
|
1030
|
+
this.wave9ProtectionLossInFlight.delete(key);
|
|
1031
|
+
const pending = this.pendingWave9ProtectionLossBySymbol.get(key);
|
|
1032
|
+
if (pending) {
|
|
1033
|
+
this.pendingWave9ProtectionLossBySymbol.delete(key);
|
|
1034
|
+
this.notifyWave9ProtectionLoss(pending);
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
860
1038
|
/**
|
|
861
1039
|
* AUTHORITATIVE bracket-lifecycle handler — Binance's ALGO_UPDATE push.
|
|
862
1040
|
*
|
|
@@ -896,6 +1074,11 @@ export class LiveAdapter extends EventEmitter {
|
|
|
896
1074
|
const symbol = normalizeBracketSymbol(ev.symbol);
|
|
897
1075
|
const row = this.bracketLedger?.getBySymbol(symbol);
|
|
898
1076
|
const status = ev.status.toUpperCase();
|
|
1077
|
+
const rowIsRecoverable = !!row
|
|
1078
|
+
&& (row.state === 'active' || row.state === 'partial' || row.state === 'attaching');
|
|
1079
|
+
const isCurrentStop = parsed.role === 'stop'
|
|
1080
|
+
&& row?.bracketId === parsed.bracketId
|
|
1081
|
+
&& (!row.slCid || row.slCid === cid);
|
|
899
1082
|
// Record the authoritative per-leg WS status for the Tier-1 fast-path of
|
|
900
1083
|
// resolveBracketLegLiveness(). Every parsed rc-* lifecycle event, every
|
|
901
1084
|
// branch — including CANCELED, which the ledger deliberately does NOT
|
|
@@ -989,12 +1172,34 @@ export class LiveAdapter extends EventEmitter {
|
|
|
989
1172
|
else {
|
|
990
1173
|
logger.info(TAG, `ALGO_UPDATE CANCELED on ${symbol} (${cid}) — ledger=${row?.state ?? 'none'} (benign: cancel of an already-terminal/absent bracket).`);
|
|
991
1174
|
}
|
|
1175
|
+
if (rowIsRecoverable && isCurrentStop && row) {
|
|
1176
|
+
this.notifyWave9ProtectionLoss({
|
|
1177
|
+
source: 'algo_canceled',
|
|
1178
|
+
symbol,
|
|
1179
|
+
bracketId: row.bracketId,
|
|
1180
|
+
entryClientOrderId: row.entryCid,
|
|
1181
|
+
entrySide: row.entrySide,
|
|
1182
|
+
stopClientOrderId: cid,
|
|
1183
|
+
detail: `exact native stop reported CANCELED (reason=${ev.reason ?? 'none'})`,
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
992
1186
|
return;
|
|
993
1187
|
}
|
|
994
1188
|
// NEW / TRIGGERING / REJECTED — informational. REJECTED is worth a WARN
|
|
995
1189
|
// (Binance refused the conditional order — e.g. margin check).
|
|
996
1190
|
if (status === 'REJECTED') {
|
|
997
1191
|
logger.warn(TAG, `ALGO_UPDATE REJECTED on ${symbol} (${cid}) reason=${ev.reason ?? 'none'} — bracket leg refused by Binance; position may be unprotected.`);
|
|
1192
|
+
if (rowIsRecoverable && isCurrentStop && row) {
|
|
1193
|
+
this.notifyWave9ProtectionLoss({
|
|
1194
|
+
source: 'algo_rejected',
|
|
1195
|
+
symbol,
|
|
1196
|
+
bracketId: row.bracketId,
|
|
1197
|
+
entryClientOrderId: row.entryCid,
|
|
1198
|
+
entrySide: row.entrySide,
|
|
1199
|
+
stopClientOrderId: cid,
|
|
1200
|
+
detail: `exact native stop reported REJECTED (reason=${ev.reason ?? 'none'})`,
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
998
1203
|
}
|
|
999
1204
|
}
|
|
1000
1205
|
/** Expose the bracket manager for agent-facing tools (modify_stop, etc.).
|
|
@@ -1062,6 +1267,12 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1062
1267
|
// Tier 3 — no answer anywhere.
|
|
1063
1268
|
return 'unknown';
|
|
1064
1269
|
}
|
|
1270
|
+
/** Immediate full-shape proof for one exact native conditional order. */
|
|
1271
|
+
async resolveBracketLegProof(cid) {
|
|
1272
|
+
if (!cid)
|
|
1273
|
+
return null;
|
|
1274
|
+
return this.api.queryAlgoOrderProof(cid);
|
|
1275
|
+
}
|
|
1065
1276
|
/** Symbol-scoped generalization of the trusted-WS fast-path — the
|
|
1066
1277
|
* authoritative action-layer guard for the 2026-05-16 INJ bracket spiral.
|
|
1067
1278
|
*
|
|
@@ -1273,6 +1484,14 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1273
1484
|
const order = await this.api.createOrder(symbol, closeSide, 'market', position.contracts, undefined, ccxtParams);
|
|
1274
1485
|
logger.info(TAG, `FLATTEN: ${symbol} close order ${order.id} status=${order.status}`);
|
|
1275
1486
|
this.emit('emergency_progress', { action: 'flatten', status: 'completed', symbol });
|
|
1487
|
+
// Auto-flatten (bracket_attach_failed) bypasses the close_position tool,
|
|
1488
|
+
// so onClosePositionFilled never journals its close and the position row
|
|
1489
|
+
// orphans as status='open' (issue #196). Journal it here from the real
|
|
1490
|
+
// close fill. Other reasons (operator_command, agent close_position) are
|
|
1491
|
+
// journaled by their own paths — don't double-capture.
|
|
1492
|
+
if (closeReason === 'bracket_attach_failed') {
|
|
1493
|
+
this.captureAutoFlattenClose(symbol, order);
|
|
1494
|
+
}
|
|
1276
1495
|
return order;
|
|
1277
1496
|
}
|
|
1278
1497
|
catch (err) {
|
|
@@ -1350,12 +1569,17 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1350
1569
|
emitUnprotected(`threw: ${formatError(err)}`);
|
|
1351
1570
|
}
|
|
1352
1571
|
}
|
|
1353
|
-
|
|
1572
|
+
/** Null-honest balance read: null = the fetch FAILED (429 / weight-paced /
|
|
1573
|
+
* banned) — the caller MUST treat it as UNKNOWN, never as a zero balance.
|
|
1574
|
+
* Same null≠empty contract as getPositionsOrNull. Decision paths (sizing,
|
|
1575
|
+
* pre-trade risk) must use this: the legacy getBalance() collapse below
|
|
1576
|
+
* reads as walletTotal=0 → ~−100% drawdown → RED zone. */
|
|
1577
|
+
async getBalanceOrNull() {
|
|
1354
1578
|
const balance = await this.api.fetchBalance();
|
|
1355
1579
|
this.syncRateLimits();
|
|
1356
1580
|
this.rateLimiter.recordQuery(ENDPOINT_WEIGHTS.fetchBalance ?? 5);
|
|
1357
1581
|
if (!balance) {
|
|
1358
|
-
return
|
|
1582
|
+
return null;
|
|
1359
1583
|
}
|
|
1360
1584
|
// Binance USDⓈ-M quirk: `availableBalance` is only populated for the primary
|
|
1361
1585
|
// margin asset (USDT). Secondary collaterals like USDC come back with
|
|
@@ -1393,6 +1617,17 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1393
1617
|
const cachedPositions = this.reconciler.getLastExchangePositions();
|
|
1394
1618
|
return this.enricher.enrich(balance, cachedPositions);
|
|
1395
1619
|
}
|
|
1620
|
+
/** Legacy display-path read. Collapses a FAILED fetch to an empty balance
|
|
1621
|
+
* object — acceptable for read-only surfaces (fetch_balance tool,
|
|
1622
|
+
* risk-summary display), a phantom-zero hazard for anything that decides.
|
|
1623
|
+
* Decision paths use getBalanceOrNull(). */
|
|
1624
|
+
async getBalance() {
|
|
1625
|
+
const balance = await this.getBalanceOrNull();
|
|
1626
|
+
if (!balance) {
|
|
1627
|
+
return { free: {}, used: {}, total: {} };
|
|
1628
|
+
}
|
|
1629
|
+
return balance;
|
|
1630
|
+
}
|
|
1396
1631
|
/** Re-anchor the realized-today seed + sessionStartNav from /fapi/v1/income.
|
|
1397
1632
|
* Throttled to {@link INCOME_REFRESH_MIN_INTERVAL_MS}; UTC date rollover
|
|
1398
1633
|
* forces an immediate refresh. Best-effort — failures are logged and
|
|
@@ -1534,6 +1769,9 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1534
1769
|
out.push({
|
|
1535
1770
|
...p,
|
|
1536
1771
|
setupType: p.setupType ?? freshMeta.setupType,
|
|
1772
|
+
missionId: p.missionId ?? freshMeta.missionId,
|
|
1773
|
+
candidateId: p.candidateId ?? freshMeta.candidateId,
|
|
1774
|
+
strategy: p.strategy ?? freshMeta.strategy,
|
|
1537
1775
|
thesis: p.thesis ?? freshMeta.thesis,
|
|
1538
1776
|
stopPrice: p.stopPrice ?? freshMeta.stopPrice,
|
|
1539
1777
|
targetPrice: p.targetPrice ?? freshMeta.targetPrice,
|
|
@@ -1618,6 +1856,9 @@ export class LiveAdapter extends EventEmitter {
|
|
|
1618
1856
|
async fetchOrder(orderId, symbol) {
|
|
1619
1857
|
return this.api.fetchOrder(orderId, symbol);
|
|
1620
1858
|
}
|
|
1859
|
+
async resolveOrderByClientId(clientOrderId, symbol) {
|
|
1860
|
+
return this.api.resolveOrderByClientId(clientOrderId, symbol);
|
|
1861
|
+
}
|
|
1621
1862
|
async getLastPrice(symbol) {
|
|
1622
1863
|
const ticker = await this.api.fetchTicker(symbol);
|
|
1623
1864
|
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.
|
|
@@ -10,6 +10,10 @@ export interface PositionStateEntry {
|
|
|
10
10
|
webappPositionId?: string;
|
|
11
11
|
/** exchangeTradeId of the opening fill (audit trail). */
|
|
12
12
|
openedFromExchangeTradeId?: string;
|
|
13
|
+
/** setup_type from the entry metadata (e.g. 'pullback_trend_short_4h').
|
|
14
|
+
* Carried so close paths can record the exit into the re-entry tracker
|
|
15
|
+
* keyed by (symbol, setup) — issue #204. Optional; older entries lack it. */
|
|
16
|
+
setupType?: string;
|
|
13
17
|
/** Last record_position_reviews call timestamp (epoch ms). Drives stale-gate. */
|
|
14
18
|
lastReviewAt?: number;
|
|
15
19
|
lastVerdict?: 'hold' | 'add_on' | 'close_recommended';
|
|
@@ -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) {
|