@reefclaw/connect 0.1.12 → 0.1.13
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/assets/plugin/audit/mode-transition-audit.d.ts +11 -0
- package/assets/plugin/audit/mode-transition-audit.js +29 -0
- package/assets/plugin/balance-utils.d.ts +36 -0
- package/assets/plugin/balance-utils.js +98 -0
- package/assets/plugin/ccxt/binance-ban-gate.d.ts +47 -0
- package/assets/plugin/ccxt/binance-ban-gate.js +420 -0
- package/assets/plugin/ccxt/binance-private.d.ts +346 -0
- package/assets/plugin/ccxt/binance-private.js +1525 -0
- package/assets/plugin/ccxt/binance-public.d.ts +34 -0
- package/assets/plugin/ccxt/binance-public.js +180 -0
- package/assets/plugin/ccxt/intel-public.d.ts +25 -0
- package/assets/plugin/ccxt/intel-public.js +80 -0
- package/assets/plugin/ccxt/public-market-data-api.d.ts +26 -0
- package/assets/plugin/ccxt/public-market-data-api.js +23 -0
- package/assets/plugin/config/agent-config-client.d.ts +77 -0
- package/assets/plugin/config/agent-config-client.js +188 -0
- package/assets/plugin/config/agent-config-poller.d.ts +32 -0
- package/assets/plugin/config/agent-config-poller.js +101 -0
- package/assets/plugin/config/brackets-config.d.ts +22 -0
- package/assets/plugin/config/brackets-config.js +58 -0
- package/assets/plugin/config/entitlement-gate.d.ts +51 -0
- package/assets/plugin/config/entitlement-gate.js +137 -0
- package/assets/plugin/config/gate-store.d.ts +18 -0
- package/assets/plugin/config/gate-store.js +61 -0
- package/assets/plugin/config/plugin-config-io.d.ts +222 -0
- package/assets/plugin/config/plugin-config-io.js +126 -0
- package/assets/plugin/config/position-review-config.d.ts +35 -0
- package/assets/plugin/config/position-review-config.js +105 -0
- package/assets/plugin/config/tool-gate.d.ts +53 -0
- package/assets/plugin/config/tool-gate.js +128 -0
- package/assets/plugin/config/user-data-stream-config.d.ts +89 -0
- package/assets/plugin/config/user-data-stream-config.js +239 -0
- package/assets/plugin/connector-supervisor.d.ts +36 -0
- package/assets/plugin/connector-supervisor.js +149 -0
- package/assets/plugin/exchange-adapter.d.ts +65 -0
- package/assets/plugin/exchange-adapter.js +4 -0
- package/assets/plugin/index.d.ts +30 -0
- package/assets/plugin/index.js +2763 -0
- package/assets/plugin/ingest/pending-entry-metadata.d.ts +74 -0
- package/assets/plugin/ingest/pending-entry-metadata.js +236 -0
- package/assets/plugin/ingest/position-auto-capture.d.ts +171 -0
- package/assets/plugin/ingest/position-auto-capture.js +709 -0
- package/assets/plugin/ingest/position-decisions-client.d.ts +327 -0
- package/assets/plugin/ingest/position-decisions-client.js +306 -0
- package/assets/plugin/ingest/readiness-reporter.d.ts +42 -0
- package/assets/plugin/ingest/readiness-reporter.js +163 -0
- package/assets/plugin/ingest/reconcile-db-vs-exchange.d.ts +51 -0
- package/assets/plugin/ingest/reconcile-db-vs-exchange.js +170 -0
- package/assets/plugin/ingest/reconciler-cleanup.d.ts +37 -0
- package/assets/plugin/ingest/reconciler-cleanup.js +147 -0
- package/assets/plugin/ingest/rest-gap-filler.d.ts +191 -0
- package/assets/plugin/ingest/rest-gap-filler.js +565 -0
- package/assets/plugin/ingest/touched-symbols-store.d.ts +25 -0
- package/assets/plugin/ingest/touched-symbols-store.js +96 -0
- package/assets/plugin/ingest/trade-store-client.d.ts +40 -0
- package/assets/plugin/ingest/trade-store-client.js +125 -0
- package/assets/plugin/ingest/ws-ingest.d.ts +43 -0
- package/assets/plugin/ingest/ws-ingest.js +126 -0
- package/assets/plugin/learning/setup-family.d.ts +21 -0
- package/assets/plugin/learning/setup-family.js +103 -0
- package/assets/plugin/lifecycle/install-signal-handlers.d.ts +33 -0
- package/assets/plugin/lifecycle/install-signal-handlers.js +112 -0
- package/assets/plugin/lifecycle/shutdown-coordinator.d.ts +43 -0
- package/assets/plugin/lifecycle/shutdown-coordinator.js +131 -0
- package/assets/plugin/lifecycle/trading-operation-lock.d.ts +17 -0
- package/assets/plugin/lifecycle/trading-operation-lock.js +14 -0
- package/assets/plugin/live/bracket-id.d.ts +26 -0
- package/assets/plugin/live/bracket-id.js +112 -0
- package/assets/plugin/live/bracket-ledger.d.ts +54 -0
- package/assets/plugin/live/bracket-ledger.js +267 -0
- package/assets/plugin/live/bracket-manager.d.ts +82 -0
- package/assets/plugin/live/bracket-manager.js +478 -0
- package/assets/plugin/live/bracket-params.d.ts +22 -0
- package/assets/plugin/live/bracket-params.js +124 -0
- package/assets/plugin/live/bracket-reconciler.d.ts +95 -0
- package/assets/plugin/live/bracket-reconciler.js +573 -0
- package/assets/plugin/live/bracket-types.d.ts +102 -0
- package/assets/plugin/live/bracket-types.js +8 -0
- package/assets/plugin/live/deposit-tracker.d.ts +62 -0
- package/assets/plugin/live/deposit-tracker.js +97 -0
- package/assets/plugin/live/emergency-controls.d.ts +32 -0
- package/assets/plugin/live/emergency-controls.js +226 -0
- package/assets/plugin/live/exchange-errors.d.ts +12 -0
- package/assets/plugin/live/exchange-errors.js +130 -0
- package/assets/plugin/live/exchange-info-cache.d.ts +37 -0
- package/assets/plugin/live/exchange-info-cache.js +134 -0
- package/assets/plugin/live/fact-subscriber.d.ts +78 -0
- package/assets/plugin/live/fact-subscriber.js +182 -0
- package/assets/plugin/live/fill-price.d.ts +13 -0
- package/assets/plugin/live/fill-price.js +37 -0
- package/assets/plugin/live/intent-journal.d.ts +42 -0
- package/assets/plugin/live/intent-journal.js +122 -0
- package/assets/plugin/live/listen-key-manager.d.ts +70 -0
- package/assets/plugin/live/listen-key-manager.js +169 -0
- package/assets/plugin/live/live-adapter.d.ts +319 -0
- package/assets/plugin/live/live-adapter.js +1906 -0
- package/assets/plugin/live/live-balance-enricher.d.ts +32 -0
- package/assets/plugin/live/live-balance-enricher.js +104 -0
- package/assets/plugin/live/live-bracket-api.d.ts +13 -0
- package/assets/plugin/live/live-bracket-api.js +20 -0
- package/assets/plugin/live/live-state-store.d.ts +194 -0
- package/assets/plugin/live/live-state-store.js +450 -0
- package/assets/plugin/live/local-signal-service.d.ts +57 -0
- package/assets/plugin/live/local-signal-service.js +151 -0
- package/assets/plugin/live/local-strategy-evaluator.d.ts +62 -0
- package/assets/plugin/live/local-strategy-evaluator.js +131 -0
- package/assets/plugin/live/microstructure-assembler.d.ts +54 -0
- package/assets/plugin/live/microstructure-assembler.js +148 -0
- package/assets/plugin/live/order-poller.d.ts +29 -0
- package/assets/plugin/live/order-poller.js +125 -0
- package/assets/plugin/live/position-state-store.d.ts +87 -0
- package/assets/plugin/live/position-state-store.js +237 -0
- package/assets/plugin/live/proposal-decision-listener.d.ts +70 -0
- package/assets/plugin/live/proposal-decision-listener.js +292 -0
- package/assets/plugin/live/proposal-manager.d.ts +76 -0
- package/assets/plugin/live/proposal-manager.js +140 -0
- package/assets/plugin/live/rate-limiter.d.ts +47 -0
- package/assets/plugin/live/rate-limiter.js +159 -0
- package/assets/plugin/live/reconciler.d.ts +39 -0
- package/assets/plugin/live/reconciler.js +175 -0
- package/assets/plugin/live/setup-buckets.d.ts +7 -0
- package/assets/plugin/live/setup-buckets.js +33 -0
- package/assets/plugin/live/slippage-tracker.d.ts +45 -0
- package/assets/plugin/live/slippage-tracker.js +78 -0
- package/assets/plugin/live/stop-watcher.d.ts +67 -0
- package/assets/plugin/live/stop-watcher.js +218 -0
- package/assets/plugin/live/user-data-active-probe.d.ts +54 -0
- package/assets/plugin/live/user-data-active-probe.js +180 -0
- package/assets/plugin/live/user-data-stream-controller.d.ts +200 -0
- package/assets/plugin/live/user-data-stream-controller.js +579 -0
- package/assets/plugin/live/user-data-stream-ws.d.ts +22 -0
- package/assets/plugin/live/user-data-stream-ws.js +63 -0
- package/assets/plugin/live/user-data-stream.d.ts +243 -0
- package/assets/plugin/live/user-data-stream.js +704 -0
- package/assets/plugin/logger.d.ts +2 -0
- package/assets/plugin/logger.js +2 -0
- package/assets/plugin/mfe.d.ts +21 -0
- package/assets/plugin/mfe.js +68 -0
- package/assets/plugin/onboarding/mode-ladder.d.ts +1 -0
- package/assets/plugin/onboarding/mode-ladder.js +3 -0
- package/assets/plugin/onboarding/runtime.d.ts +102 -0
- package/assets/plugin/onboarding/runtime.js +215 -0
- package/assets/plugin/openclaw.plugin.json +92 -0
- package/assets/plugin/paper-adapter.d.ts +24 -0
- package/assets/plugin/paper-adapter.js +91 -0
- package/assets/plugin/persistence/state-manager.d.ts +66 -0
- package/assets/plugin/persistence/state-manager.js +222 -0
- package/assets/plugin/pinned-plan.d.ts +9 -0
- package/assets/plugin/pinned-plan.js +23 -0
- package/assets/plugin/portfolio/reentry-tracker.d.ts +36 -0
- package/assets/plugin/portfolio/reentry-tracker.js +127 -0
- package/assets/plugin/portfolio/wave9-admission.d.ts +67 -0
- package/assets/plugin/portfolio/wave9-admission.js +262 -0
- package/assets/plugin/portfolio/wave9-policy.d.ts +36 -0
- package/assets/plugin/portfolio/wave9-policy.js +183 -0
- package/assets/plugin/risk/pre-trade-check.d.ts +38 -0
- package/assets/plugin/risk/pre-trade-check.js +345 -0
- package/assets/plugin/risk/pre-trade-types.d.ts +60 -0
- package/assets/plugin/risk/pre-trade-types.js +3 -0
- package/assets/plugin/shadow/shadow-tracker.d.ts +36 -0
- package/assets/plugin/shadow/shadow-tracker.js +151 -0
- package/assets/plugin/shadow/types.d.ts +42 -0
- package/assets/plugin/shadow/types.js +20 -0
- package/assets/plugin/shared/indicators-extended.d.ts +52 -0
- package/assets/plugin/shared/indicators-extended.js +291 -0
- package/assets/plugin/shared/indicators.d.ts +15 -0
- package/assets/plugin/shared/indicators.js +114 -0
- package/assets/plugin/signals/conditions/registry.d.ts +16 -0
- package/assets/plugin/signals/conditions/registry.js +1333 -0
- package/assets/plugin/signals/conditions/types.d.ts +1 -0
- package/assets/plugin/signals/conditions/types.js +4 -0
- package/assets/plugin/signals/direction-rules.d.ts +3 -0
- package/assets/plugin/signals/direction-rules.js +24 -0
- package/assets/plugin/signals/entry-rules.d.ts +6 -0
- package/assets/plugin/signals/entry-rules.js +33 -0
- package/assets/plugin/signals/serialize-context.d.ts +4 -0
- package/assets/plugin/signals/serialize-context.js +39 -0
- package/assets/plugin/signals/stop-rules.d.ts +3 -0
- package/assets/plugin/signals/stop-rules.js +48 -0
- package/assets/plugin/signals/strategy-adapter.d.ts +47 -0
- package/assets/plugin/signals/strategy-adapter.js +209 -0
- package/assets/plugin/signals/types.d.ts +1 -0
- package/assets/plugin/signals/types.js +8 -0
- package/assets/plugin/simulator/exchange-simulator.d.ts +105 -0
- package/assets/plugin/simulator/exchange-simulator.js +759 -0
- package/assets/plugin/simulator/fill-engine.d.ts +53 -0
- package/assets/plugin/simulator/fill-engine.js +280 -0
- package/assets/plugin/simulator/paper-market-feed.d.ts +26 -0
- package/assets/plugin/simulator/paper-market-feed.js +104 -0
- package/assets/plugin/simulator/realistic-fills.d.ts +59 -0
- package/assets/plugin/simulator/realistic-fills.js +175 -0
- package/assets/plugin/simulator/types.d.ts +228 -0
- package/assets/plugin/simulator/types.js +43 -0
- package/assets/plugin/strategy/builtin-strategies.d.ts +2 -0
- package/assets/plugin/strategy/builtin-strategies.js +113 -0
- package/assets/plugin/strategy/condition-registry.d.ts +3 -0
- package/assets/plugin/strategy/condition-registry.js +153 -0
- package/assets/plugin/strategy/evaluator.d.ts +70 -0
- package/assets/plugin/strategy/evaluator.js +98 -0
- package/assets/plugin/tools/assessment-validation.d.ts +141 -0
- package/assets/plugin/tools/assessment-validation.js +473 -0
- package/assets/plugin/tools/attach-brackets.d.ts +39 -0
- package/assets/plugin/tools/attach-brackets.js +564 -0
- package/assets/plugin/tools/audit-bracket-protection.d.ts +49 -0
- package/assets/plugin/tools/audit-bracket-protection.js +683 -0
- package/assets/plugin/tools/bracket-control.d.ts +12 -0
- package/assets/plugin/tools/bracket-control.js +35 -0
- package/assets/plugin/tools/cancel-all-orders.d.ts +9 -0
- package/assets/plugin/tools/cancel-all-orders.js +8 -0
- package/assets/plugin/tools/cancel-order.d.ts +14 -0
- package/assets/plugin/tools/cancel-order.js +60 -0
- package/assets/plugin/tools/check-position-health.d.ts +46 -0
- package/assets/plugin/tools/check-position-health.js +194 -0
- package/assets/plugin/tools/clear-exchange-credentials.d.ts +24 -0
- package/assets/plugin/tools/clear-exchange-credentials.js +70 -0
- package/assets/plugin/tools/close-position.d.ts +45 -0
- package/assets/plugin/tools/close-position.js +722 -0
- package/assets/plugin/tools/create-order.d.ts +89 -0
- package/assets/plugin/tools/create-order.js +1555 -0
- package/assets/plugin/tools/exit-gate.d.ts +58 -0
- package/assets/plugin/tools/exit-gate.js +162 -0
- package/assets/plugin/tools/fetch-balance.d.ts +5 -0
- package/assets/plugin/tools/fetch-balance.js +4 -0
- package/assets/plugin/tools/fetch-ohlcv.d.ts +11 -0
- package/assets/plugin/tools/fetch-ohlcv.js +8 -0
- package/assets/plugin/tools/fetch-open-orders.d.ts +7 -0
- package/assets/plugin/tools/fetch-open-orders.js +4 -0
- package/assets/plugin/tools/fetch-positions.d.ts +7 -0
- package/assets/plugin/tools/fetch-positions.js +4 -0
- package/assets/plugin/tools/fetch-ticker.d.ts +11 -0
- package/assets/plugin/tools/fetch-ticker.js +5 -0
- package/assets/plugin/tools/get-agent-profile.d.ts +4 -0
- package/assets/plugin/tools/get-agent-profile.js +6 -0
- package/assets/plugin/tools/get-analytics.d.ts +6 -0
- package/assets/plugin/tools/get-analytics.js +7 -0
- package/assets/plugin/tools/get-backtest.d.ts +12 -0
- package/assets/plugin/tools/get-backtest.js +91 -0
- package/assets/plugin/tools/get-basis.d.ts +7 -0
- package/assets/plugin/tools/get-basis.js +7 -0
- package/assets/plugin/tools/get-bracket-config.d.ts +11 -0
- package/assets/plugin/tools/get-bracket-config.js +24 -0
- package/assets/plugin/tools/get-cascade-risk.d.ts +7 -0
- package/assets/plugin/tools/get-cascade-risk.js +8 -0
- package/assets/plugin/tools/get-crypto-metrics.d.ts +18 -0
- package/assets/plugin/tools/get-crypto-metrics.js +62 -0
- package/assets/plugin/tools/get-cvd.d.ts +6 -0
- package/assets/plugin/tools/get-cvd.js +6 -0
- package/assets/plugin/tools/get-divergences.d.ts +6 -0
- package/assets/plugin/tools/get-divergences.js +6 -0
- package/assets/plugin/tools/get-funding-context.d.ts +6 -0
- package/assets/plugin/tools/get-funding-context.js +16 -0
- package/assets/plugin/tools/get-liquidation-levels.d.ts +7 -0
- package/assets/plugin/tools/get-liquidation-levels.js +7 -0
- package/assets/plugin/tools/get-liquidation-pulse.d.ts +9 -0
- package/assets/plugin/tools/get-liquidation-pulse.js +22 -0
- package/assets/plugin/tools/get-market-breadth.d.ts +6 -0
- package/assets/plugin/tools/get-market-breadth.js +8 -0
- package/assets/plugin/tools/get-market-intel.d.ts +19 -0
- package/assets/plugin/tools/get-market-intel.js +116 -0
- package/assets/plugin/tools/get-market-structure.d.ts +47 -0
- package/assets/plugin/tools/get-market-structure.js +198 -0
- package/assets/plugin/tools/get-my-mined-patterns.d.ts +20 -0
- package/assets/plugin/tools/get-my-mined-patterns.js +61 -0
- package/assets/plugin/tools/get-my-proposed-learnings.d.ts +20 -0
- package/assets/plugin/tools/get-my-proposed-learnings.js +55 -0
- package/assets/plugin/tools/get-my-recent-reviews.d.ts +22 -0
- package/assets/plugin/tools/get-my-recent-reviews.js +66 -0
- package/assets/plugin/tools/get-orderbook.d.ts +21 -0
- package/assets/plugin/tools/get-orderbook.js +32 -0
- package/assets/plugin/tools/get-pattern-scan.d.ts +7 -0
- package/assets/plugin/tools/get-pattern-scan.js +8 -0
- package/assets/plugin/tools/get-regime.d.ts +6 -0
- package/assets/plugin/tools/get-regime.js +7 -0
- package/assets/plugin/tools/get-relevant-learnings.d.ts +21 -0
- package/assets/plugin/tools/get-relevant-learnings.js +65 -0
- package/assets/plugin/tools/get-resting-liquidity.d.ts +6 -0
- package/assets/plugin/tools/get-resting-liquidity.js +11 -0
- package/assets/plugin/tools/get-risk-scenario.d.ts +29 -0
- package/assets/plugin/tools/get-risk-scenario.js +47 -0
- package/assets/plugin/tools/get-risk-summary.d.ts +51 -0
- package/assets/plugin/tools/get-risk-summary.js +118 -0
- package/assets/plugin/tools/get-sentiment.d.ts +4 -0
- package/assets/plugin/tools/get-sentiment.js +6 -0
- package/assets/plugin/tools/get-session-review.d.ts +7 -0
- package/assets/plugin/tools/get-session-review.js +8 -0
- package/assets/plugin/tools/get-setup-detail.d.ts +7 -0
- package/assets/plugin/tools/get-setup-detail.js +322 -0
- package/assets/plugin/tools/get-signals.d.ts +15 -0
- package/assets/plugin/tools/get-signals.js +54 -0
- package/assets/plugin/tools/get-sizing.d.ts +6 -0
- package/assets/plugin/tools/get-sizing.js +6 -0
- package/assets/plugin/tools/get-trade-feedback.d.ts +7 -0
- package/assets/plugin/tools/get-trade-feedback.js +8 -0
- package/assets/plugin/tools/get-trade-flow.d.ts +7 -0
- package/assets/plugin/tools/get-trade-flow.js +7 -0
- package/assets/plugin/tools/get-volume-analysis.d.ts +21 -0
- package/assets/plugin/tools/get-volume-analysis.js +74 -0
- package/assets/plugin/tools/get-volume-profile.d.ts +7 -0
- package/assets/plugin/tools/get-volume-profile.js +7 -0
- package/assets/plugin/tools/get-wave9-status.d.ts +127 -0
- package/assets/plugin/tools/get-wave9-status.js +796 -0
- package/assets/plugin/tools/helpers.d.ts +26 -0
- package/assets/plugin/tools/helpers.js +39 -0
- package/assets/plugin/tools/intel-api.d.ts +34 -0
- package/assets/plugin/tools/intel-api.js +119 -0
- package/assets/plugin/tools/intel-cache.d.ts +25 -0
- package/assets/plugin/tools/intel-cache.js +148 -0
- package/assets/plugin/tools/list-strategies.d.ts +17 -0
- package/assets/plugin/tools/list-strategies.js +23 -0
- package/assets/plugin/tools/modify-stop.d.ts +21 -0
- package/assets/plugin/tools/modify-stop.js +120 -0
- package/assets/plugin/tools/modify-target.d.ts +21 -0
- package/assets/plugin/tools/modify-target.js +110 -0
- package/assets/plugin/tools/propose-learning.d.ts +22 -0
- package/assets/plugin/tools/propose-learning.js +65 -0
- package/assets/plugin/tools/query-review-outcomes.d.ts +30 -0
- package/assets/plugin/tools/query-review-outcomes.js +64 -0
- package/assets/plugin/tools/query-trades.d.ts +21 -0
- package/assets/plugin/tools/query-trades.js +37 -0
- package/assets/plugin/tools/record-position-reviews.d.ts +38 -0
- package/assets/plugin/tools/record-position-reviews.js +147 -0
- package/assets/plugin/tools/save-strategy.d.ts +16 -0
- package/assets/plugin/tools/save-strategy.js +46 -0
- package/assets/plugin/tools/scan-pairs.d.ts +22 -0
- package/assets/plugin/tools/scan-pairs.js +234 -0
- package/assets/plugin/tools/score-setup.d.ts +31 -0
- package/assets/plugin/tools/score-setup.js +268 -0
- package/assets/plugin/tools/set-bracket-requirement.d.ts +18 -0
- package/assets/plugin/tools/set-bracket-requirement.js +81 -0
- package/assets/plugin/tools/set-exchange-credentials.d.ts +25 -0
- package/assets/plugin/tools/set-exchange-credentials.js +80 -0
- package/assets/plugin/tools/set-trading-mode.d.ts +26 -0
- package/assets/plugin/tools/set-trading-mode.js +152 -0
- package/assets/plugin/tools/test-exchange-credentials.d.ts +16 -0
- package/assets/plugin/tools/test-exchange-credentials.js +100 -0
- package/assets/plugin/tools/toggle-strategy.d.ts +8 -0
- package/assets/plugin/tools/toggle-strategy.js +15 -0
- package/assets/plugin/trading-params-cache.d.ts +26 -0
- package/assets/plugin/trading-params-cache.js +52 -0
- package/assets/plugin/types.d.ts +134 -0
- package/assets/plugin/types.js +7 -0
- package/assets/plugin/util/plugin-paths.d.ts +3 -0
- package/assets/plugin/util/plugin-paths.js +15 -0
- package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +116 -0
- package/assets/plugin/venues/hyperliquid/hl-balance.js +145 -0
- package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
- package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
- package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +102 -0
- package/assets/plugin/venues/hyperliquid/hl-brackets.js +172 -0
- package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +22 -0
- package/assets/plugin/venues/hyperliquid/hl-cloid.js +82 -0
- package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +46 -0
- package/assets/plugin/venues/hyperliquid/hl-info-cache.js +125 -0
- package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +146 -0
- package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +728 -0
- package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +61 -0
- package/assets/plugin/venues/hyperliquid/hl-precision.js +176 -0
- package/assets/plugin/venues/hyperliquid/hl-private.d.ts +88 -0
- package/assets/plugin/venues/hyperliquid/hl-private.js +357 -0
- package/assets/plugin/venues/hyperliquid/hl-public.d.ts +79 -0
- package/assets/plugin/venues/hyperliquid/hl-public.js +436 -0
- package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
- package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +220 -0
- package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +90 -0
- package/assets/plugin/venues/hyperliquid/hl-user-stream.js +220 -0
- package/assets/plugin/venues/registry.d.ts +38 -0
- package/assets/plugin/venues/registry.js +46 -0
- package/assets/plugin/venues/symbols.d.ts +43 -0
- package/assets/plugin/venues/symbols.js +107 -0
- package/assets/plugin/wave9/live-account-capture.d.ts +67 -0
- package/assets/plugin/wave9/live-account-capture.js +435 -0
- package/assets/plugin/wave9/live-autonomous-protection.d.ts +39 -0
- package/assets/plugin/wave9/live-autonomous-protection.js +112 -0
- package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
- package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +115 -0
- package/assets/plugin/wave9/live-execution-ledger.d.ts +107 -0
- package/assets/plugin/wave9/live-execution-ledger.js +498 -0
- package/assets/plugin/wave9/live-position-confirmation.d.ts +18 -0
- package/assets/plugin/wave9/live-position-confirmation.js +111 -0
- package/assets/plugin/wave9/live-residual-protection.d.ts +18 -0
- package/assets/plugin/wave9/live-residual-protection.js +250 -0
- package/assets/plugin/wave9/live-startup-reconciliation.d.ts +38 -0
- package/assets/plugin/wave9/live-startup-reconciliation.js +454 -0
- package/assets/plugin/wave9/live-symbol-ownership.d.ts +20 -0
- package/assets/plugin/wave9/live-symbol-ownership.js +132 -0
- package/assets/plugin/wave9/paper-admission-guard.d.ts +199 -0
- package/assets/plugin/wave9/paper-admission-guard.js +650 -0
- package/assets/plugin/wave9/usdm-evidence-provider.d.ts +42 -0
- package/assets/plugin/wave9/usdm-evidence-provider.js +133 -0
- package/package.json +1 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Agent-readiness reporter — runs connect-phase health checks ON the trader's
|
|
2
|
+
// host and POSTs a self-describing report to the webapp, so a silently-broken
|
|
3
|
+
// agent (geo-blocked by Binance, clock-skewed, no tools) surfaces a plain-English
|
|
4
|
+
// dashboard alert instead of a false green. Advisory only: it NEVER blocks
|
|
5
|
+
// trading or the safety floor. See docs/AGENT_READINESS_GATE_PLAN.md (Phase 1).
|
|
6
|
+
//
|
|
7
|
+
// No token → no-op (nothing to authenticate; matches the config poller). A
|
|
8
|
+
// module-level singleton guard mirrors `pluginInitialised` in index.ts — OpenClaw
|
|
9
|
+
// calls register() multiple times per process and we must never spawn a second
|
|
10
|
+
// timer. The interval is unref()'d so it never holds the process open.
|
|
11
|
+
import { makeReadinessCheck, deriveOverallReadiness, } from '@reefclaw/shared';
|
|
12
|
+
import { logger, formatError } from '../logger.js';
|
|
13
|
+
const TAG = 'readiness';
|
|
14
|
+
const DEFAULT_INTERVAL_MS = 300_000; // 5 min — geo/clock state changes rarely.
|
|
15
|
+
const MIN_INTERVAL_MS = 60_000;
|
|
16
|
+
/** Best-effort display fact; kept in sync with the register() banner in index.ts. */
|
|
17
|
+
const PLUGIN_VERSION = '3.8.0';
|
|
18
|
+
function resolveIntervalMs(explicit) {
|
|
19
|
+
if (explicit && explicit > 0)
|
|
20
|
+
return Math.max(MIN_INTERVAL_MS, explicit);
|
|
21
|
+
const raw = Number(process.env.RC_READINESS_INTERVAL_MS);
|
|
22
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
23
|
+
return DEFAULT_INTERVAL_MS;
|
|
24
|
+
return Math.max(MIN_INTERVAL_MS, raw);
|
|
25
|
+
}
|
|
26
|
+
/** Run every connect-phase check once and assemble the report. Exported for
|
|
27
|
+
* unit tests.
|
|
28
|
+
*
|
|
29
|
+
* `bootWarmup` marks the FIRST cycle, which fires during gateway boot while
|
|
30
|
+
* the event loop is congested (WS start, snapshot, seed all racing). That
|
|
31
|
+
* delay between the probe's server-time response and the local `Date.now()`
|
|
32
|
+
* read inflates the apparent clock drift — observed −4112ms at boot
|
|
33
|
+
* self-correcting to −108ms on the next 5-min cycle. During warm-up a
|
|
34
|
+
* warn/fail drift is reported 'unknown' (not amber/red) so the readiness
|
|
35
|
+
* banner doesn't cry-wolf for ~5 min after every restart; a genuinely
|
|
36
|
+
* skewed clock still surfaces on cycle 2. */
|
|
37
|
+
export async function collectReadiness(opts, bootWarmup = false) {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const checks = [];
|
|
40
|
+
// The venue decides which reachability check this report carries; the copy
|
|
41
|
+
// for both ids lives in shared/src/readiness.ts. Clock drift comes from the
|
|
42
|
+
// same probe on both venues (Binance fapi serverTime / Hyperliquid
|
|
43
|
+
// exchangeStatus.time — live-verified 2026-07-11).
|
|
44
|
+
const reachId = opts.venue === 'hyperliquid' ? 'hyperliquid_reachable' : 'binance_reachable';
|
|
45
|
+
// plugin_loaded — trivially true (this code runs inside the loaded plugin),
|
|
46
|
+
// but a positive row is what proves the report path is alive at all.
|
|
47
|
+
checks.push(makeReadinessCheck('plugin_loaded', 'pass', { checkedAt: now }));
|
|
48
|
+
// tools_registered
|
|
49
|
+
checks.push(makeReadinessCheck('tools_registered', opts.toolCount > 0 ? 'pass' : 'fail', {
|
|
50
|
+
detail: `${opts.toolCount} tools`,
|
|
51
|
+
checkedAt: now,
|
|
52
|
+
}));
|
|
53
|
+
// venue reachability (+ clock drift from the same probe response)
|
|
54
|
+
const probe = await opts.publicApi.probeReachability();
|
|
55
|
+
if (probe.outcome === 'reachable') {
|
|
56
|
+
checks.push(makeReadinessCheck(reachId, 'pass', { checkedAt: now }));
|
|
57
|
+
const drift = probe.driftMs;
|
|
58
|
+
if (drift == null) {
|
|
59
|
+
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const abs = Math.abs(drift);
|
|
63
|
+
const rawStatus = abs <= 1000 ? 'pass' : abs <= 5000 ? 'warn' : 'fail';
|
|
64
|
+
// Boot warm-up: a non-pass reading on the first cycle is untrustworthy
|
|
65
|
+
// (event-loop congestion inflates the measured drift) — report 'unknown'
|
|
66
|
+
// and let the next cycle confirm, rather than flash the banner amber/red.
|
|
67
|
+
const status = bootWarmup && rawStatus !== 'pass' ? 'unknown' : rawStatus;
|
|
68
|
+
checks.push(makeReadinessCheck('clock_in_sync', status, {
|
|
69
|
+
detail: bootWarmup && rawStatus !== 'pass'
|
|
70
|
+
? `drift ${Math.round(drift)}ms (boot warm-up — rechecking)`
|
|
71
|
+
: `drift ${Math.round(drift)}ms`,
|
|
72
|
+
checkedAt: now,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (probe.outcome === 'geo_blocked') {
|
|
77
|
+
checks.push(makeReadinessCheck(reachId, 'fail', {
|
|
78
|
+
detail: opts.venue === 'binance' ? 'HTTP 451' : 'blocked (HTTP 451/403)',
|
|
79
|
+
checkedAt: now,
|
|
80
|
+
}));
|
|
81
|
+
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
82
|
+
}
|
|
83
|
+
else if (probe.outcome === 'unreachable') {
|
|
84
|
+
// Network/DNS/timeout — could be transient, so warn (amber) rather than
|
|
85
|
+
// asserting a definitive failure. A persistent problem stays amber across
|
|
86
|
+
// re-checks; only the geo-block is a hard red.
|
|
87
|
+
checks.push(makeReadinessCheck(reachId, 'warn', { detail: 'unreachable', checkedAt: now }));
|
|
88
|
+
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// 'unknown' — the ban/weight gate paused the probe; don't assert anything.
|
|
92
|
+
checks.push(makeReadinessCheck(reachId, 'unknown', { checkedAt: now }));
|
|
93
|
+
checks.push(makeReadinessCheck('clock_in_sync', 'unknown', { checkedAt: now }));
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
schemaVersion: 1,
|
|
97
|
+
generatedAt: now,
|
|
98
|
+
overall: deriveOverallReadiness(checks),
|
|
99
|
+
checks,
|
|
100
|
+
agent: { pluginVersion: PLUGIN_VERSION, toolCount: opts.toolCount, venue: opts.venue },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function postReadiness(apiBaseUrl, token, report, fetchImpl, timeoutMs) {
|
|
104
|
+
const url = `${apiBaseUrl.replace(/\/+$/, '')}/api/internal/agent-readiness`;
|
|
105
|
+
const ac = new AbortController();
|
|
106
|
+
const tid = setTimeout(() => ac.abort(), timeoutMs);
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetchImpl(url, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'content-type': 'application/json',
|
|
112
|
+
authorization: `Bearer ${token}`,
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify(report),
|
|
115
|
+
signal: ac.signal,
|
|
116
|
+
});
|
|
117
|
+
if (res.status < 200 || res.status >= 300) {
|
|
118
|
+
logger.warn(TAG, `POST ${url} → ${res.status}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
logger.warn(TAG, `readiness POST failed: ${formatError(err)}`);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
clearTimeout(tid);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let reporterStarted = false;
|
|
129
|
+
/** Test-only — reset the singleton guard between unit tests. */
|
|
130
|
+
export function __resetReadinessReporterForTests() {
|
|
131
|
+
reporterStarted = false;
|
|
132
|
+
}
|
|
133
|
+
/** Fire the readiness report once at boot then on an unref'd interval. */
|
|
134
|
+
export function startReadinessReporter(opts) {
|
|
135
|
+
if (reporterStarted)
|
|
136
|
+
return;
|
|
137
|
+
if (!opts.token || !opts.token.trim()) {
|
|
138
|
+
logger.info(TAG, 'no connection token — readiness reporting disabled');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
reporterStarted = true;
|
|
142
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
143
|
+
const intervalMs = resolveIntervalMs(opts.intervalMs);
|
|
144
|
+
const timeoutMs = opts.requestTimeoutMs ?? 10_000;
|
|
145
|
+
const cycle = async (bootWarmup) => {
|
|
146
|
+
try {
|
|
147
|
+
const report = await collectReadiness({ venue: opts.venue, publicApi: opts.publicApi, toolCount: opts.toolCount }, bootWarmup);
|
|
148
|
+
await postReadiness(opts.apiBaseUrl, opts.token, report, fetchImpl, timeoutMs);
|
|
149
|
+
if (report.overall === 'fail') {
|
|
150
|
+
const failing = report.checks.filter((c) => c.status === 'fail').map((c) => c.id).join(', ');
|
|
151
|
+
logger.warn(TAG, `readiness FAIL: ${failing}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logger.warn(TAG, `readiness cycle failed: ${formatError(err)}`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
// First fire is the boot cycle (warm-up); the interval cycles are steady-state.
|
|
159
|
+
void cycle(true);
|
|
160
|
+
const timer = setInterval(() => void cycle(false), intervalMs);
|
|
161
|
+
timer.unref();
|
|
162
|
+
logger.info(TAG, `readiness reporter started (interval ${Math.round(intervalMs / 1000)}s)`);
|
|
163
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { PositionDecisionsClient } from './position-decisions-client.js';
|
|
2
|
+
export interface DbVsExchangeContext {
|
|
3
|
+
decisionsClient?: PositionDecisionsClient;
|
|
4
|
+
userId?: string;
|
|
5
|
+
/** Active trading book. Scopes the DB read so open rows from the INACTIVE
|
|
6
|
+
* book — which can never appear in this adapter's snapshot — are not
|
|
7
|
+
* misread as orphans and synthetically closed (cross-book corruption after
|
|
8
|
+
* a set_trading_mode switch with positions open). Absent → unscoped
|
|
9
|
+
* (legacy callers). */
|
|
10
|
+
resolveMode?: () => 'paper' | 'live';
|
|
11
|
+
/** Active venue (issue #209 item 5) — the same orphan-misread class on the
|
|
12
|
+
* exchange axis: a tenant running two venues has open rows the OTHER
|
|
13
|
+
* venue's snapshot can never contain. Absent → unscoped (legacy). */
|
|
14
|
+
resolveExchange?: () => 'binance' | 'hyperliquid';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Close webapp `positions` rows that are status='open' but absent from the
|
|
18
|
+
* (trusted) exchange snapshot. Returns the number of synthetic closes posted.
|
|
19
|
+
*
|
|
20
|
+
* @param exchangeSymbols symbols from a TRUSTED snapshot (getPositionsOrNull()
|
|
21
|
+
* !== null). Pass only when the fetch genuinely succeeded.
|
|
22
|
+
*/
|
|
23
|
+
export interface DbReconcileOptions {
|
|
24
|
+
/** Provenance tag written into closeAssessment.source (default boot sweep). */
|
|
25
|
+
source?: string;
|
|
26
|
+
/** Attribution hook (issue #203): given an orphaned symbol, return a short
|
|
27
|
+
* human-readable description of what the execution engine last knew about
|
|
28
|
+
* it (e.g. the simulator's last trade). Logged with the synthetic close so
|
|
29
|
+
* chronic capture misses can be classified instead of perpetually healed. */
|
|
30
|
+
describeLastExit?: (symbol: string) => string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare function reconcileDbOpenVsExchange(ctx: DbVsExchangeContext, exchangeSymbols: Iterable<string>, nowMs?: number, opts?: DbReconcileOptions): Promise<number>;
|
|
33
|
+
export declare const DEFAULT_DB_RECONCILE_INTERVAL_MS = 300000;
|
|
34
|
+
export interface PeriodicDbReconcileDeps extends DbVsExchangeContext {
|
|
35
|
+
/** Resolve the ACTIVE adapter each tick (follows runtime reconnects). */
|
|
36
|
+
resolveAdapter: () => {
|
|
37
|
+
getPositionsOrNull(symbol?: string): Promise<Array<{
|
|
38
|
+
symbol: string;
|
|
39
|
+
}> | null>;
|
|
40
|
+
} | undefined;
|
|
41
|
+
describeLastExit?: (symbol: string) => string | undefined;
|
|
42
|
+
}
|
|
43
|
+
export interface PeriodicDbReconcileHandle {
|
|
44
|
+
stop(): void;
|
|
45
|
+
/** One sweep cycle — exposed for tests (no setInterval). */
|
|
46
|
+
runOnce(): Promise<number>;
|
|
47
|
+
}
|
|
48
|
+
/** Resolve the sweep interval from RC_DB_RECONCILE_INTERVAL_MS.
|
|
49
|
+
* 'off' or '0' disables (returns null); values are clamped to ≥60s. */
|
|
50
|
+
export declare function resolveDbReconcileIntervalMs(raw?: string | undefined): number | null;
|
|
51
|
+
export declare function startPeriodicDbReconcile(deps: PeriodicDbReconcileDeps, intervalMs?: number | null): PeriodicDbReconcileHandle | null;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// DB-authoritative orphan close (Increment 0 of the durable orphan-position fix).
|
|
2
|
+
//
|
|
3
|
+
// The problem the existing backstops can't solve: a position closed on the
|
|
4
|
+
// exchange (bracket SL/TP fill or manual close) while the gateway was DOWN, or
|
|
5
|
+
// whose state-store entry was already wiped, leaves webapp `positions.status`
|
|
6
|
+
// stuck 'open' forever. reconcileStateStoreOnStartup can't help — it iterates
|
|
7
|
+
// only LOCAL state-store entries (a wiped entry is invisible) and passes
|
|
8
|
+
// lastContracts:0 (which trips the fillSize>0 guard, so it never posts a close).
|
|
9
|
+
//
|
|
10
|
+
// This pass is DB-authoritative: it asks the webapp for the open set (keyed by
|
|
11
|
+
// the DB row id, not local state), diffs against the EXCHANGE TRUTH snapshot,
|
|
12
|
+
// and posts a synthetic close for any open DB row whose symbol is absent from
|
|
13
|
+
// the exchange. Keying off the DB row id means a missing webappPositionId /
|
|
14
|
+
// wiped state-store / untracked contracts can't defeat it for the common
|
|
15
|
+
// one-row-per-symbol case.
|
|
16
|
+
//
|
|
17
|
+
// Known limitations (all fail SAFE — only false-negatives, never a wrong close):
|
|
18
|
+
// - Diff is symbol-membership only. If a symbol has BOTH a genuine orphan and
|
|
19
|
+
// a freshly re-opened live position (two open rows, same symbol, different
|
|
20
|
+
// positionOpenAt), the symbol is on the exchange so NEITHER is closed — the
|
|
21
|
+
// orphan persists until the next boot when that symbol is flat. A later
|
|
22
|
+
// increment can make this count-aware (close the M−N oldest-by-openedAt when
|
|
23
|
+
// the DB has M open rows but the exchange has N<M for the symbol).
|
|
24
|
+
// - First-writer-wins on position_closes UNIQUE(position_id): if a real
|
|
25
|
+
// close_position's journal POST is still retrying at boot, a synthetic close
|
|
26
|
+
// can land first and the accurate PnL becomes a no-op. Synthetic rows are
|
|
27
|
+
// tagged (metadata.synthetic) so a later increment could upgrade them.
|
|
28
|
+
// - closeAt is stamped at boot, not the real (earlier) exchange close time, so
|
|
29
|
+
// time-in-position analytics on these rows are approximate. Tagged synthetic.
|
|
30
|
+
//
|
|
31
|
+
// Safety (mandatory): the caller MUST pass a TRUSTED exchange snapshot
|
|
32
|
+
// (getPositionsOrNull() !== null). A failed REST fetch collapses to [] in
|
|
33
|
+
// getPositions(), which would make EVERY open row look orphaned — never run
|
|
34
|
+
// this pass off an untrusted/empty-on-error snapshot.
|
|
35
|
+
//
|
|
36
|
+
// Idempotency: position_closes is UNIQUE(position_id) with onConflictDoNothing,
|
|
37
|
+
// and the parent status flip is idempotent — a real close_position (exact PnL)
|
|
38
|
+
// that lands first always wins the UNIQUE. closeReason='reconciler_observed_flat'
|
|
39
|
+
// + metadata.synthetic=true tags these for KPI exclusion.
|
|
40
|
+
import { logger } from '../logger.js';
|
|
41
|
+
const TAG = 'reconcile-db-vs-exchange';
|
|
42
|
+
/** Canonicalise a symbol to its settle-suffix-free form (BTC/USDT:USDT → BTC/USDT). */
|
|
43
|
+
function canonical(symbol) {
|
|
44
|
+
return symbol.split(':')[0];
|
|
45
|
+
}
|
|
46
|
+
export async function reconcileDbOpenVsExchange(ctx, exchangeSymbols, nowMs = Date.now(), opts = {}) {
|
|
47
|
+
if (!ctx.decisionsClient || !ctx.userId)
|
|
48
|
+
return 0;
|
|
49
|
+
const resp = await ctx.decisionsClient.getOpenPositions(ctx.userId, ctx.resolveMode?.(), ctx.resolveExchange?.());
|
|
50
|
+
if (!resp) {
|
|
51
|
+
// null = fetch failed (network / terminal). NEVER treat as "no open rows"
|
|
52
|
+
// (null≠empty) — skip; the next boot / periodic sweep retries.
|
|
53
|
+
logger.warn(TAG, 'DB open-positions fetch returned null — skipping reconcile (will retry)');
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
if (resp.positions.length === 0)
|
|
57
|
+
return 0;
|
|
58
|
+
const exchangeSet = new Set();
|
|
59
|
+
for (const s of exchangeSymbols)
|
|
60
|
+
exchangeSet.add(canonical(s));
|
|
61
|
+
const orphans = resp.positions.filter((p) => !exchangeSet.has(canonical(p.symbol)));
|
|
62
|
+
if (orphans.length === 0) {
|
|
63
|
+
logger.info(TAG, `DB reconcile: all ${resp.positions.length} open row(s) present on exchange`);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
logger.warn(TAG, `DB reconcile: ${orphans.length} open DB row(s) absent from exchange — closing: ${orphans.map((o) => o.symbol).join(', ')}`);
|
|
67
|
+
let posted = 0;
|
|
68
|
+
for (const o of orphans) {
|
|
69
|
+
if (!(o.remainingSize > 0) || !(o.entryPrice > 0)) {
|
|
70
|
+
logger.warn(TAG, `${o.symbol}: bad size/price from DB (size=${o.remainingSize}, entry=${o.entryPrice}) — skipping close`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const lastExitInfo = opts.describeLastExit?.(o.symbol);
|
|
74
|
+
if (lastExitInfo) {
|
|
75
|
+
logger.warn(TAG, `${o.symbol}: capture-miss attribution — ${lastExitInfo}`);
|
|
76
|
+
}
|
|
77
|
+
const close = {
|
|
78
|
+
positionId: o.id,
|
|
79
|
+
closeAt: nowMs,
|
|
80
|
+
closeReason: 'reconciler_observed_flat',
|
|
81
|
+
closeAssessment: {
|
|
82
|
+
source: opts.source ?? 'db_exchange_sweep_boot',
|
|
83
|
+
...(lastExitInfo ? { last_engine_trade: lastExitInfo } : {}),
|
|
84
|
+
synthetic: true,
|
|
85
|
+
note: 'Position open in DB but absent from the exchange snapshot. It was ' +
|
|
86
|
+
'closed on-exchange via a path that bypassed close_position (bracket ' +
|
|
87
|
+
'SL/TP fill or manual close). Synthetic close; PnL/R/MFE recomputed ' +
|
|
88
|
+
'server-side from the entry + last review.',
|
|
89
|
+
openedAtMs: o.openedAt,
|
|
90
|
+
},
|
|
91
|
+
// Synthetic-but-neutral context. Tagged so KPI aggregation can exclude it.
|
|
92
|
+
scorecardVerdict: 'unknown',
|
|
93
|
+
confluenceScore: 0,
|
|
94
|
+
regime: 'UNKNOWN',
|
|
95
|
+
regimeConfidence: 0,
|
|
96
|
+
// The true exit price wasn't captured; use the entry so the server-side
|
|
97
|
+
// recompute yields ~0 realized PnL (honest "unknown") rather than a
|
|
98
|
+
// fabricated number. R/MFE/give-back come from the last review row.
|
|
99
|
+
fillPrice: o.entryPrice,
|
|
100
|
+
fillSize: o.remainingSize,
|
|
101
|
+
realizedPnl: 0,
|
|
102
|
+
realizedR: 0,
|
|
103
|
+
mfeRAtClose: 0,
|
|
104
|
+
giveBackPctAtClose: 0,
|
|
105
|
+
metadata: { synthetic: true, source: 'boot_reconcile' },
|
|
106
|
+
};
|
|
107
|
+
ctx.decisionsClient.postClose(ctx.userId, close);
|
|
108
|
+
posted++;
|
|
109
|
+
logger.info(TAG, `synthetic close posted ${o.symbol} (positionId=${o.id.slice(0, 8)}…, size=${o.remainingSize})`);
|
|
110
|
+
}
|
|
111
|
+
return posted;
|
|
112
|
+
}
|
|
113
|
+
// ---- Periodic sweep (issues #199/#203) ----
|
|
114
|
+
//
|
|
115
|
+
// The boot-only sweep left phantoms alive for DAYS between restarts (paper:
|
|
116
|
+
// the LTC phantom of issue #199 lived 26.5h; live: 38 of 85 closes in 14 days
|
|
117
|
+
// were harvested only at the daily restart, issue #203). This interval runs
|
|
118
|
+
// the same DB-authoritative pass continuously with the same trusted-snapshot
|
|
119
|
+
// discipline: getPositionsOrNull() null → skip, never treat as flat.
|
|
120
|
+
export const DEFAULT_DB_RECONCILE_INTERVAL_MS = 300_000;
|
|
121
|
+
const MIN_DB_RECONCILE_INTERVAL_MS = 60_000;
|
|
122
|
+
/** Resolve the sweep interval from RC_DB_RECONCILE_INTERVAL_MS.
|
|
123
|
+
* 'off' or '0' disables (returns null); values are clamped to ≥60s. */
|
|
124
|
+
export function resolveDbReconcileIntervalMs(raw = process.env.RC_DB_RECONCILE_INTERVAL_MS) {
|
|
125
|
+
if (raw === 'off' || raw === '0')
|
|
126
|
+
return null;
|
|
127
|
+
const n = Number(raw);
|
|
128
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
129
|
+
return DEFAULT_DB_RECONCILE_INTERVAL_MS;
|
|
130
|
+
return Math.max(MIN_DB_RECONCILE_INTERVAL_MS, n);
|
|
131
|
+
}
|
|
132
|
+
export function startPeriodicDbReconcile(deps, intervalMs = resolveDbReconcileIntervalMs()) {
|
|
133
|
+
if (intervalMs === null) {
|
|
134
|
+
logger.info(TAG, 'periodic sweep disabled (RC_DB_RECONCILE_INTERVAL_MS=off)');
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
let running = false;
|
|
138
|
+
const runOnce = async () => {
|
|
139
|
+
if (running)
|
|
140
|
+
return 0; // concurrency guard — a slow sweep never overlaps
|
|
141
|
+
running = true;
|
|
142
|
+
try {
|
|
143
|
+
const adapter = deps.resolveAdapter();
|
|
144
|
+
if (!adapter)
|
|
145
|
+
return 0;
|
|
146
|
+
const positions = await adapter.getPositionsOrNull();
|
|
147
|
+
if (positions === null) {
|
|
148
|
+
// Untrusted snapshot — NEVER read as flat (null≠empty).
|
|
149
|
+
logger.warn(TAG, 'periodic sweep skipped — positions fetch untrusted (null)');
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
return await reconcileDbOpenVsExchange(deps, positions.map((p) => p.symbol), Date.now(), { source: 'db_exchange_sweep_periodic', describeLastExit: deps.describeLastExit });
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
logger.warn(TAG, `periodic sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
running = false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const timer = setInterval(() => { void runOnce(); }, intervalMs);
|
|
163
|
+
// Never keep the process alive just for the sweep.
|
|
164
|
+
timer.unref?.();
|
|
165
|
+
logger.info(TAG, `periodic DB-vs-exchange sweep started (interval ${intervalMs}ms)`);
|
|
166
|
+
return {
|
|
167
|
+
stop: () => clearInterval(timer),
|
|
168
|
+
runOnce,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PositionDecisionsClient } from './position-decisions-client.js';
|
|
2
|
+
import type { PositionStateStore } from '../live/position-state-store.js';
|
|
3
|
+
export interface ReconcilerCleanupContext {
|
|
4
|
+
decisionsClient?: PositionDecisionsClient;
|
|
5
|
+
stateStore?: PositionStateStore;
|
|
6
|
+
userId?: string;
|
|
7
|
+
/** Last-price fetcher. Plugin-side passes a closure over
|
|
8
|
+
* `adapter.getLastPrice`. Tests inject a mock. Returning `null` skips
|
|
9
|
+
* the webapp close post (state-store still cleaned). */
|
|
10
|
+
lastPriceFn?: (symbol: string) => Promise<number | null>;
|
|
11
|
+
}
|
|
12
|
+
export interface CloseObservedArgs {
|
|
13
|
+
/** Symbol observed flat. Either canonical (`BTC/USDT`) or settle-suffix
|
|
14
|
+
* (`BTC/USDT:USDT`) form — state-store normalises internally. */
|
|
15
|
+
symbol: string;
|
|
16
|
+
/** Last contracts seen by reconciler before exchange showed flat. Used as
|
|
17
|
+
* fillSize on the synthetic close payload. */
|
|
18
|
+
lastContracts: number;
|
|
19
|
+
/** Epoch ms — when reconciler observed the close. Defaults to Date.now(). */
|
|
20
|
+
observedAtMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function onReconcilerObservedClose(ctx: ReconcilerCleanupContext, args: CloseObservedArgs): Promise<void>;
|
|
23
|
+
/** Startup-time reconciliation between state-store and exchange truth.
|
|
24
|
+
*
|
|
25
|
+
* Why this exists: the in-memory reconciler only emits `closed` drifts on
|
|
26
|
+
* the exact transition "was on exchange last poll, gone now". State-store
|
|
27
|
+
* entries that persist across a close-bypass that happened BEFORE the plugin
|
|
28
|
+
* started are invisible to that loop — they sit in `position-state.json`
|
|
29
|
+
* forever and the next entry on the same symbol gets mistaken for a scale-in.
|
|
30
|
+
*
|
|
31
|
+
* This pass runs once at boot, after the live adapter is initialised:
|
|
32
|
+
* for every state-store entry, if the symbol isn't on the current exchange
|
|
33
|
+
* snapshot, fire the same cleanup as the runtime hook would.
|
|
34
|
+
*
|
|
35
|
+
* Returns the count of entries cleaned (for log + telemetry).
|
|
36
|
+
*/
|
|
37
|
+
export declare function reconcileStateStoreOnStartup(ctx: ReconcilerCleanupContext, exchangeSymbols: Iterable<string>): Promise<number>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Reconciler-driven cleanup for the close-bypass-leaks-state class-of-bug.
|
|
2
|
+
//
|
|
3
|
+
// Background: when a position closes via any path other than close_position()
|
|
4
|
+
// — i.e., bracket SL/TP fill, opposite-side market order, or external close on
|
|
5
|
+
// the exchange UI — onClosePositionFilled never runs. Two pieces of state leak:
|
|
6
|
+
//
|
|
7
|
+
// 1. webapp `positions.status` stays 'open' indefinitely (until a manual
|
|
8
|
+
// `scripts/reconcile-open-positions.py` sweep).
|
|
9
|
+
// 2. `position-state.json` keeps the entry with its stale `webappPositionId`.
|
|
10
|
+
// The next entry on the same symbol is then mistaken for a scale-in and
|
|
11
|
+
// reviews silently drop because the cached id no longer maps to a live
|
|
12
|
+
// DB row.
|
|
13
|
+
//
|
|
14
|
+
// All three close-bypass paths converge on the same observable: the next
|
|
15
|
+
// reconciler tick (60 s cadence) sees the symbol gone from `getPositions()`
|
|
16
|
+
// and emits a `drift_detected` event with `type='closed'`. We hook there.
|
|
17
|
+
//
|
|
18
|
+
// Two-phase cleanup:
|
|
19
|
+
// Phase 1 — synthetic close to webapp (best-effort): if the state-store
|
|
20
|
+
// entry has a `webappPositionId` and we can fetch a last price,
|
|
21
|
+
// POST `/api/internal/position-closes` with a stub close payload
|
|
22
|
+
// tagged `closeReason='reconciler_observed_flat'`. Webapp
|
|
23
|
+
// recomputes realized PnL/R/MFE server-side from positionEntries +
|
|
24
|
+
// last review (the same path the close auto-capture uses).
|
|
25
|
+
// Phase 2 — state-store cleanup (unconditional): drop the entry. This is
|
|
26
|
+
// the more important half — it prevents the next entry on the
|
|
27
|
+
// same symbol from being mistaken for a scale-in.
|
|
28
|
+
//
|
|
29
|
+
// Idempotency:
|
|
30
|
+
// - webapp endpoint dedups via UNIQUE on position_id (onConflictDoNothing).
|
|
31
|
+
// - state-store remove is a no-op if the entry is already gone.
|
|
32
|
+
// Re-firing on subsequent reconciler ticks is safe.
|
|
33
|
+
//
|
|
34
|
+
// Failure mode tradeoff: when no last-price is reachable, we skip the webapp
|
|
35
|
+
// post but still clean state-store. The DB row stays 'open' until manual
|
|
36
|
+
// reconcile (existing operator workflow). State-store cleanup alone closes
|
|
37
|
+
// the next-entry-mistaken-for-scale-in failure mode, which is the harder one
|
|
38
|
+
// to recover from.
|
|
39
|
+
import { logger } from '../logger.js';
|
|
40
|
+
const TAG = 'reconciler-cleanup';
|
|
41
|
+
export async function onReconcilerObservedClose(ctx, args) {
|
|
42
|
+
if (!ctx.stateStore)
|
|
43
|
+
return;
|
|
44
|
+
const entry = ctx.stateStore.get(args.symbol);
|
|
45
|
+
if (!entry) {
|
|
46
|
+
// Already clean. Could happen if close_position fired between the WS
|
|
47
|
+
// close fill and this reconciler tick — onClosePositionFilled removed the
|
|
48
|
+
// entry first. Silent no-op.
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const webappPositionId = entry.webappPositionId;
|
|
52
|
+
const observedAtMs = args.observedAtMs ?? Date.now();
|
|
53
|
+
if (webappPositionId && ctx.decisionsClient && ctx.userId) {
|
|
54
|
+
let lastPrice = null;
|
|
55
|
+
if (ctx.lastPriceFn) {
|
|
56
|
+
try {
|
|
57
|
+
lastPrice = await ctx.lastPriceFn(args.symbol);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
logger.warn(TAG, `lastPriceFn(${args.symbol}) threw: ${err instanceof Error ? err.message : String(err)} — synthetic close will be skipped`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const fillSize = Math.abs(args.lastContracts);
|
|
64
|
+
if (lastPrice !== null && lastPrice > 0 && fillSize > 0) {
|
|
65
|
+
const close = {
|
|
66
|
+
positionId: webappPositionId,
|
|
67
|
+
closeAt: observedAtMs,
|
|
68
|
+
closeReason: 'reconciler_observed_flat',
|
|
69
|
+
closeAssessment: {
|
|
70
|
+
note: 'Position observed flat on exchange via reconciler. Close path ' +
|
|
71
|
+
'bypassed close_position (likely bracket SL/TP fill, opposite-side ' +
|
|
72
|
+
'market order, or external close). Synthetic close row.',
|
|
73
|
+
observedAtMs,
|
|
74
|
+
lastContracts: args.lastContracts,
|
|
75
|
+
},
|
|
76
|
+
scorecardVerdict: 'NO_GO',
|
|
77
|
+
confluenceScore: 0,
|
|
78
|
+
regime: 'unknown',
|
|
79
|
+
regimeConfidence: 0.5,
|
|
80
|
+
fillPrice: lastPrice,
|
|
81
|
+
fillSize,
|
|
82
|
+
// Webapp recomputes these server-side from positionEntries + last
|
|
83
|
+
// review when all four arrive as zero (same code path the close
|
|
84
|
+
// auto-capture uses).
|
|
85
|
+
realizedPnl: 0,
|
|
86
|
+
realizedR: 0,
|
|
87
|
+
mfeRAtClose: 0,
|
|
88
|
+
giveBackPctAtClose: 0,
|
|
89
|
+
};
|
|
90
|
+
ctx.decisionsClient.postClose(ctx.userId, close);
|
|
91
|
+
logger.info(TAG, `synthetic close posted ${args.symbol} ` +
|
|
92
|
+
`(positionId=${webappPositionId.slice(0, 8)}…, ` +
|
|
93
|
+
`price=${lastPrice}, size=${fillSize})`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
logger.warn(TAG, `${args.symbol}: cannot post synthetic close ` +
|
|
97
|
+
`(lastPrice=${lastPrice ?? 'null'}, size=${fillSize}); ` +
|
|
98
|
+
`state-store entry will still be cleaned. DB row may stay ` +
|
|
99
|
+
`status='open' until reconcile-open-positions.py runs.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (!webappPositionId) {
|
|
103
|
+
logger.info(TAG, `${args.symbol}: orphan entry (no webappPositionId, parent row never created) — cleaning state-store only`);
|
|
104
|
+
}
|
|
105
|
+
ctx.stateStore.remove(args.symbol);
|
|
106
|
+
}
|
|
107
|
+
/** Startup-time reconciliation between state-store and exchange truth.
|
|
108
|
+
*
|
|
109
|
+
* Why this exists: the in-memory reconciler only emits `closed` drifts on
|
|
110
|
+
* the exact transition "was on exchange last poll, gone now". State-store
|
|
111
|
+
* entries that persist across a close-bypass that happened BEFORE the plugin
|
|
112
|
+
* started are invisible to that loop — they sit in `position-state.json`
|
|
113
|
+
* forever and the next entry on the same symbol gets mistaken for a scale-in.
|
|
114
|
+
*
|
|
115
|
+
* This pass runs once at boot, after the live adapter is initialised:
|
|
116
|
+
* for every state-store entry, if the symbol isn't on the current exchange
|
|
117
|
+
* snapshot, fire the same cleanup as the runtime hook would.
|
|
118
|
+
*
|
|
119
|
+
* Returns the count of entries cleaned (for log + telemetry).
|
|
120
|
+
*/
|
|
121
|
+
export async function reconcileStateStoreOnStartup(ctx, exchangeSymbols) {
|
|
122
|
+
if (!ctx.stateStore)
|
|
123
|
+
return 0;
|
|
124
|
+
// Normalise the exchange symbol list — state-store stores canonical form.
|
|
125
|
+
const exchangeSet = new Set();
|
|
126
|
+
for (const sym of exchangeSymbols) {
|
|
127
|
+
exchangeSet.add(sym.split(':')[0]);
|
|
128
|
+
}
|
|
129
|
+
const stale = ctx.stateStore.getAll().filter((e) => !exchangeSet.has(e.symbol));
|
|
130
|
+
if (stale.length === 0) {
|
|
131
|
+
logger.info(TAG, `startup reconcile: state-store clean (${ctx.stateStore.size()} entries match exchange)`);
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
logger.warn(TAG, `startup reconcile: ${stale.length} stale state-store entr${stale.length === 1 ? 'y' : 'ies'} ` +
|
|
135
|
+
`not on exchange — cleaning: ${stale.map((e) => e.symbol).join(', ')}`);
|
|
136
|
+
for (const entry of stale) {
|
|
137
|
+
await onReconcilerObservedClose(ctx, {
|
|
138
|
+
symbol: entry.symbol,
|
|
139
|
+
// We don't know the closing size — pass 0 so the synthetic close
|
|
140
|
+
// post is skipped (size validation in cleanup). State-store entry
|
|
141
|
+
// is still removed. Operator runs reconcile-open-positions.py to
|
|
142
|
+
// close any matching DB rows.
|
|
143
|
+
lastContracts: 0,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return stale.length;
|
|
147
|
+
}
|