@reefclaw/openclaw-plugin 0.1.0
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/.gitignore +8 -0
- package/audit/mode-transition-audit.d.ts +11 -0
- package/audit/mode-transition-audit.js +29 -0
- package/balance-utils.d.ts +36 -0
- package/balance-utils.js +98 -0
- package/bridge/bridge.d.ts +109 -0
- package/bridge/bridge.js +1036 -0
- package/bridge/config.d.ts +43 -0
- package/bridge/config.js +176 -0
- package/bridge/connector.d.ts +52 -0
- package/bridge/connector.js +293 -0
- package/bridge/event-replay-buffer.d.ts +43 -0
- package/bridge/event-replay-buffer.js +109 -0
- package/bridge/gateway/event-parser.d.ts +209 -0
- package/bridge/gateway/event-parser.js +793 -0
- package/bridge/gateway/gateway-config.d.ts +39 -0
- package/bridge/gateway/gateway-config.js +86 -0
- package/bridge/gateway/gateway-http-client.d.ts +50 -0
- package/bridge/gateway/gateway-http-client.js +165 -0
- package/bridge/gateway/gateway-ws-client.d.ts +116 -0
- package/bridge/gateway/gateway-ws-client.js +417 -0
- package/bridge/gateway/poller.d.ts +146 -0
- package/bridge/gateway/poller.js +505 -0
- package/bridge/gateway/tool-discovery.d.ts +25 -0
- package/bridge/gateway/tool-discovery.js +198 -0
- package/bridge/index.d.ts +2 -0
- package/bridge/index.js +253 -0
- package/bridge/logger.d.ts +2 -0
- package/bridge/logger.js +2 -0
- package/bridge/provider.d.ts +156 -0
- package/bridge/provider.js +2 -0
- package/bridge/providers/emergency-commands.d.ts +54 -0
- package/bridge/providers/emergency-commands.js +235 -0
- package/bridge/providers/gateway.d.ts +322 -0
- package/bridge/providers/gateway.js +2302 -0
- package/bridge/providers/mock.d.ts +37 -0
- package/bridge/providers/mock.js +385 -0
- package/bridge/providers/onboarding-commands.d.ts +83 -0
- package/bridge/providers/onboarding-commands.js +213 -0
- package/bridge/providers/risk-calculator.d.ts +96 -0
- package/bridge/providers/risk-calculator.js +369 -0
- package/bridge/setup.d.ts +32 -0
- package/bridge/setup.js +226 -0
- package/bridge/types.d.ts +584 -0
- package/bridge/types.js +50 -0
- package/bridge/utils/reconnect.d.ts +6 -0
- package/bridge/utils/reconnect.js +6 -0
- package/bridge/utils/skill-signing.d.ts +51 -0
- package/bridge/utils/skill-signing.js +138 -0
- package/bridge/utils/skill-version.d.ts +17 -0
- package/bridge/utils/skill-version.js +74 -0
- package/ccxt/binance-ban-gate.d.ts +47 -0
- package/ccxt/binance-ban-gate.js +409 -0
- package/ccxt/binance-private.d.ts +325 -0
- package/ccxt/binance-private.js +1415 -0
- package/ccxt/binance-public.d.ts +18 -0
- package/ccxt/binance-public.js +147 -0
- package/config/agent-config-client.d.ts +55 -0
- package/config/agent-config-client.js +145 -0
- package/config/agent-config-poller.d.ts +25 -0
- package/config/agent-config-poller.js +100 -0
- package/config/brackets-config.d.ts +22 -0
- package/config/brackets-config.js +58 -0
- package/config/gate-store.d.ts +18 -0
- package/config/gate-store.js +61 -0
- package/config/plugin-config-io.d.ts +181 -0
- package/config/plugin-config-io.js +84 -0
- package/config/position-review-config.d.ts +35 -0
- package/config/position-review-config.js +105 -0
- package/config/tool-gate.d.ts +53 -0
- package/config/tool-gate.js +125 -0
- package/config/user-data-stream-config.d.ts +85 -0
- package/config/user-data-stream-config.js +224 -0
- package/connector-supervisor.d.ts +36 -0
- package/connector-supervisor.js +149 -0
- package/exchange-adapter.d.ts +49 -0
- package/exchange-adapter.js +4 -0
- package/index.d.ts +30 -0
- package/index.js +2030 -0
- package/ingest/pending-entry-metadata.d.ts +52 -0
- package/ingest/pending-entry-metadata.js +182 -0
- package/ingest/position-auto-capture.d.ts +98 -0
- package/ingest/position-auto-capture.js +394 -0
- package/ingest/position-decisions-client.d.ts +318 -0
- package/ingest/position-decisions-client.js +296 -0
- package/ingest/reconcile-db-vs-exchange.d.ts +13 -0
- package/ingest/reconcile-db-vs-exchange.js +114 -0
- package/ingest/reconciler-cleanup.d.ts +37 -0
- package/ingest/reconciler-cleanup.js +147 -0
- package/ingest/rest-gap-filler.d.ts +191 -0
- package/ingest/rest-gap-filler.js +565 -0
- package/ingest/touched-symbols-store.d.ts +25 -0
- package/ingest/touched-symbols-store.js +96 -0
- package/ingest/trade-store-client.d.ts +40 -0
- package/ingest/trade-store-client.js +125 -0
- package/ingest/ws-ingest.d.ts +43 -0
- package/ingest/ws-ingest.js +126 -0
- package/learning/setup-family.d.ts +21 -0
- package/learning/setup-family.js +103 -0
- package/lifecycle/install-signal-handlers.d.ts +33 -0
- package/lifecycle/install-signal-handlers.js +112 -0
- package/lifecycle/shutdown-coordinator.d.ts +43 -0
- package/lifecycle/shutdown-coordinator.js +131 -0
- package/live/bracket-id.d.ts +18 -0
- package/live/bracket-id.js +81 -0
- package/live/bracket-ledger.d.ts +54 -0
- package/live/bracket-ledger.js +267 -0
- package/live/bracket-manager.d.ts +82 -0
- package/live/bracket-manager.js +478 -0
- package/live/bracket-params.d.ts +22 -0
- package/live/bracket-params.js +124 -0
- package/live/bracket-reconciler.d.ts +95 -0
- package/live/bracket-reconciler.js +573 -0
- package/live/bracket-types.d.ts +102 -0
- package/live/bracket-types.js +8 -0
- package/live/deposit-tracker.d.ts +62 -0
- package/live/deposit-tracker.js +97 -0
- package/live/emergency-controls.d.ts +32 -0
- package/live/emergency-controls.js +226 -0
- package/live/exchange-errors.d.ts +12 -0
- package/live/exchange-errors.js +130 -0
- package/live/exchange-info-cache.d.ts +35 -0
- package/live/exchange-info-cache.js +119 -0
- package/live/fact-subscriber.d.ts +78 -0
- package/live/fact-subscriber.js +182 -0
- package/live/intent-journal.d.ts +42 -0
- package/live/intent-journal.js +122 -0
- package/live/listen-key-manager.d.ts +70 -0
- package/live/listen-key-manager.js +169 -0
- package/live/live-adapter.d.ts +264 -0
- package/live/live-adapter.js +1665 -0
- package/live/live-balance-enricher.d.ts +32 -0
- package/live/live-balance-enricher.js +104 -0
- package/live/live-bracket-api.d.ts +13 -0
- package/live/live-bracket-api.js +20 -0
- package/live/live-state-store.d.ts +194 -0
- package/live/live-state-store.js +450 -0
- package/live/local-signal-service.d.ts +57 -0
- package/live/local-signal-service.js +146 -0
- package/live/local-strategy-evaluator.d.ts +62 -0
- package/live/local-strategy-evaluator.js +127 -0
- package/live/microstructure-assembler.d.ts +54 -0
- package/live/microstructure-assembler.js +148 -0
- package/live/order-poller.d.ts +29 -0
- package/live/order-poller.js +125 -0
- package/live/position-state-store.d.ts +83 -0
- package/live/position-state-store.js +237 -0
- package/live/proposal-decision-listener.d.ts +64 -0
- package/live/proposal-decision-listener.js +288 -0
- package/live/proposal-manager.d.ts +76 -0
- package/live/proposal-manager.js +140 -0
- package/live/rate-limiter.d.ts +47 -0
- package/live/rate-limiter.js +159 -0
- package/live/reconciler.d.ts +39 -0
- package/live/reconciler.js +175 -0
- package/live/setup-buckets.d.ts +7 -0
- package/live/setup-buckets.js +33 -0
- package/live/slippage-tracker.d.ts +45 -0
- package/live/slippage-tracker.js +78 -0
- package/live/stop-watcher.d.ts +34 -0
- package/live/stop-watcher.js +158 -0
- package/live/user-data-active-probe.d.ts +54 -0
- package/live/user-data-active-probe.js +180 -0
- package/live/user-data-stream-controller.d.ts +200 -0
- package/live/user-data-stream-controller.js +579 -0
- package/live/user-data-stream-ws.d.ts +22 -0
- package/live/user-data-stream-ws.js +63 -0
- package/live/user-data-stream.d.ts +243 -0
- package/live/user-data-stream.js +704 -0
- package/logger.d.ts +2 -0
- package/logger.js +2 -0
- package/mfe.d.ts +21 -0
- package/mfe.js +68 -0
- package/onboarding/mode-ladder.d.ts +1 -0
- package/onboarding/mode-ladder.js +3 -0
- package/onboarding/runtime.d.ts +71 -0
- package/onboarding/runtime.js +153 -0
- package/openclaw.plugin.json +94 -0
- package/package.json +27 -0
- package/paper-adapter.d.ts +24 -0
- package/paper-adapter.js +91 -0
- package/persistence/state-manager.d.ts +42 -0
- package/persistence/state-manager.js +164 -0
- package/pinned-plan.d.ts +9 -0
- package/pinned-plan.js +23 -0
- package/risk/pre-trade-check.d.ts +38 -0
- package/risk/pre-trade-check.js +345 -0
- package/risk/pre-trade-types.d.ts +60 -0
- package/risk/pre-trade-types.js +3 -0
- package/scripts/assemble.mjs +114 -0
- package/shadow/shadow-tracker.d.ts +36 -0
- package/shadow/shadow-tracker.js +151 -0
- package/shadow/types.d.ts +42 -0
- package/shadow/types.js +20 -0
- package/shared/indicators-extended.d.ts +52 -0
- package/shared/indicators-extended.js +291 -0
- package/shared/indicators.d.ts +15 -0
- package/shared/indicators.js +114 -0
- package/signals/conditions/registry.d.ts +16 -0
- package/signals/conditions/registry.js +1274 -0
- package/signals/conditions/types.d.ts +1 -0
- package/signals/conditions/types.js +4 -0
- package/signals/direction-rules.d.ts +3 -0
- package/signals/direction-rules.js +24 -0
- package/signals/entry-rules.d.ts +6 -0
- package/signals/entry-rules.js +33 -0
- package/signals/serialize-context.d.ts +4 -0
- package/signals/serialize-context.js +39 -0
- package/signals/stop-rules.d.ts +3 -0
- package/signals/stop-rules.js +48 -0
- package/signals/strategy-adapter.d.ts +14 -0
- package/signals/strategy-adapter.js +122 -0
- package/signals/types.d.ts +1 -0
- package/signals/types.js +8 -0
- package/simulator/exchange-simulator.d.ts +93 -0
- package/simulator/exchange-simulator.js +684 -0
- package/simulator/fill-engine.d.ts +53 -0
- package/simulator/fill-engine.js +276 -0
- package/simulator/paper-market-feed.d.ts +26 -0
- package/simulator/paper-market-feed.js +104 -0
- package/simulator/realistic-fills.d.ts +59 -0
- package/simulator/realistic-fills.js +175 -0
- package/simulator/types.d.ts +219 -0
- package/simulator/types.js +43 -0
- package/skills/reefclaw/SKILL.md +97 -0
- package/strategy/builtin-strategies.d.ts +2 -0
- package/strategy/builtin-strategies.js +109 -0
- package/strategy/condition-registry.d.ts +3 -0
- package/strategy/condition-registry.js +153 -0
- package/strategy/evaluator.d.ts +67 -0
- package/strategy/evaluator.js +93 -0
- package/tools/assessment-validation.d.ts +118 -0
- package/tools/assessment-validation.js +415 -0
- package/tools/attach-brackets.d.ts +34 -0
- package/tools/attach-brackets.js +363 -0
- package/tools/audit-bracket-protection.d.ts +49 -0
- package/tools/audit-bracket-protection.js +527 -0
- package/tools/cancel-all-orders.d.ts +7 -0
- package/tools/cancel-all-orders.js +5 -0
- package/tools/cancel-order.d.ts +10 -0
- package/tools/cancel-order.js +14 -0
- package/tools/check-position-health.d.ts +46 -0
- package/tools/check-position-health.js +194 -0
- package/tools/clear-exchange-credentials.d.ts +24 -0
- package/tools/clear-exchange-credentials.js +70 -0
- package/tools/close-position.d.ts +22 -0
- package/tools/close-position.js +449 -0
- package/tools/create-order.d.ts +54 -0
- package/tools/create-order.js +338 -0
- package/tools/exit-gate.d.ts +58 -0
- package/tools/exit-gate.js +162 -0
- package/tools/fetch-balance.d.ts +5 -0
- package/tools/fetch-balance.js +4 -0
- package/tools/fetch-ohlcv.d.ts +11 -0
- package/tools/fetch-ohlcv.js +8 -0
- package/tools/fetch-open-orders.d.ts +7 -0
- package/tools/fetch-open-orders.js +4 -0
- package/tools/fetch-positions.d.ts +7 -0
- package/tools/fetch-positions.js +4 -0
- package/tools/fetch-ticker.d.ts +11 -0
- package/tools/fetch-ticker.js +5 -0
- package/tools/get-agent-profile.d.ts +4 -0
- package/tools/get-agent-profile.js +6 -0
- package/tools/get-analytics.d.ts +6 -0
- package/tools/get-analytics.js +7 -0
- package/tools/get-backtest.d.ts +12 -0
- package/tools/get-backtest.js +91 -0
- package/tools/get-basis.d.ts +7 -0
- package/tools/get-basis.js +7 -0
- package/tools/get-bracket-config.d.ts +11 -0
- package/tools/get-bracket-config.js +24 -0
- package/tools/get-cascade-risk.d.ts +7 -0
- package/tools/get-cascade-risk.js +8 -0
- package/tools/get-crypto-metrics.d.ts +18 -0
- package/tools/get-crypto-metrics.js +45 -0
- package/tools/get-cvd.d.ts +6 -0
- package/tools/get-cvd.js +6 -0
- package/tools/get-divergences.d.ts +6 -0
- package/tools/get-divergences.js +6 -0
- package/tools/get-funding-context.d.ts +6 -0
- package/tools/get-funding-context.js +16 -0
- package/tools/get-liquidation-levels.d.ts +7 -0
- package/tools/get-liquidation-levels.js +7 -0
- package/tools/get-liquidation-pulse.d.ts +9 -0
- package/tools/get-liquidation-pulse.js +22 -0
- package/tools/get-market-breadth.d.ts +6 -0
- package/tools/get-market-breadth.js +8 -0
- package/tools/get-market-intel.d.ts +19 -0
- package/tools/get-market-intel.js +116 -0
- package/tools/get-market-structure.d.ts +47 -0
- package/tools/get-market-structure.js +198 -0
- package/tools/get-my-mined-patterns.d.ts +20 -0
- package/tools/get-my-mined-patterns.js +61 -0
- package/tools/get-my-proposed-learnings.d.ts +20 -0
- package/tools/get-my-proposed-learnings.js +55 -0
- package/tools/get-my-recent-reviews.d.ts +22 -0
- package/tools/get-my-recent-reviews.js +66 -0
- package/tools/get-orderbook.d.ts +21 -0
- package/tools/get-orderbook.js +32 -0
- package/tools/get-pattern-scan.d.ts +7 -0
- package/tools/get-pattern-scan.js +8 -0
- package/tools/get-regime.d.ts +6 -0
- package/tools/get-regime.js +7 -0
- package/tools/get-relevant-learnings.d.ts +21 -0
- package/tools/get-relevant-learnings.js +65 -0
- package/tools/get-resting-liquidity.d.ts +6 -0
- package/tools/get-resting-liquidity.js +11 -0
- package/tools/get-risk-scenario.d.ts +29 -0
- package/tools/get-risk-scenario.js +47 -0
- package/tools/get-risk-summary.d.ts +51 -0
- package/tools/get-risk-summary.js +118 -0
- package/tools/get-sentiment.d.ts +4 -0
- package/tools/get-sentiment.js +6 -0
- package/tools/get-session-review.d.ts +7 -0
- package/tools/get-session-review.js +8 -0
- package/tools/get-setup-detail.d.ts +7 -0
- package/tools/get-setup-detail.js +303 -0
- package/tools/get-signals.d.ts +15 -0
- package/tools/get-signals.js +54 -0
- package/tools/get-sizing.d.ts +6 -0
- package/tools/get-sizing.js +6 -0
- package/tools/get-trade-feedback.d.ts +7 -0
- package/tools/get-trade-feedback.js +8 -0
- package/tools/get-trade-flow.d.ts +7 -0
- package/tools/get-trade-flow.js +7 -0
- package/tools/get-volume-analysis.d.ts +21 -0
- package/tools/get-volume-analysis.js +74 -0
- package/tools/get-volume-profile.d.ts +7 -0
- package/tools/get-volume-profile.js +7 -0
- package/tools/helpers.d.ts +25 -0
- package/tools/helpers.js +38 -0
- package/tools/intel-api.d.ts +14 -0
- package/tools/intel-api.js +52 -0
- package/tools/intel-cache.d.ts +25 -0
- package/tools/intel-cache.js +133 -0
- package/tools/list-strategies.d.ts +7 -0
- package/tools/list-strategies.js +6 -0
- package/tools/modify-stop.d.ts +17 -0
- package/tools/modify-stop.js +81 -0
- package/tools/modify-target.d.ts +17 -0
- package/tools/modify-target.js +71 -0
- package/tools/propose-learning.d.ts +22 -0
- package/tools/propose-learning.js +65 -0
- package/tools/query-review-outcomes.d.ts +30 -0
- package/tools/query-review-outcomes.js +64 -0
- package/tools/query-trades.d.ts +21 -0
- package/tools/query-trades.js +37 -0
- package/tools/record-position-reviews.d.ts +38 -0
- package/tools/record-position-reviews.js +147 -0
- package/tools/save-strategy.d.ts +16 -0
- package/tools/save-strategy.js +46 -0
- package/tools/scan-pairs.d.ts +18 -0
- package/tools/scan-pairs.js +220 -0
- package/tools/score-setup.d.ts +31 -0
- package/tools/score-setup.js +268 -0
- package/tools/set-bracket-requirement.d.ts +18 -0
- package/tools/set-bracket-requirement.js +81 -0
- package/tools/set-exchange-credentials.d.ts +25 -0
- package/tools/set-exchange-credentials.js +80 -0
- package/tools/set-trading-mode.d.ts +26 -0
- package/tools/set-trading-mode.js +135 -0
- package/tools/test-exchange-credentials.d.ts +16 -0
- package/tools/test-exchange-credentials.js +100 -0
- package/tools/toggle-strategy.d.ts +8 -0
- package/tools/toggle-strategy.js +8 -0
- package/trading-params-cache.d.ts +26 -0
- package/trading-params-cache.js +52 -0
- package/types.d.ts +110 -0
- package/types.js +7 -0
- package/util/plugin-paths.d.ts +3 -0
- package/util/plugin-paths.js +15 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// WebSocket client for the OpenClaw gateway.
|
|
2
|
+
// Implements the 3-step handshake, event routing, RPC methods,
|
|
3
|
+
// and reconnection with exponential backoff.
|
|
4
|
+
import WebSocket from 'ws';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
6
|
+
import { computeDelay } from '../utils/reconnect.js';
|
|
7
|
+
const TAG = 'gateway-ws';
|
|
8
|
+
// ---- Protocol constants ----
|
|
9
|
+
// Protocol range we can speak. The gateway picks its own version if it falls
|
|
10
|
+
// inside [min, max]. v3 = OpenClaw ≤2026.4.x (prod); v4 = OpenClaw 2026.6+
|
|
11
|
+
// (fresh installs) — the connect/hello frames and every RPC we use are
|
|
12
|
+
// shape-identical across both (verified live against 2026.6.11; a v3-only pin
|
|
13
|
+
// gets `1002 protocol mismatch` there and the bridge can never connect).
|
|
14
|
+
const MIN_PROTOCOL_VERSION = 3;
|
|
15
|
+
const MAX_PROTOCOL_VERSION = 4;
|
|
16
|
+
const CLIENT_ID = 'openclaw-tui';
|
|
17
|
+
const CLIENT_VERSION = '0.1.0';
|
|
18
|
+
const DEFAULT_RECONNECT = {
|
|
19
|
+
baseDelayMs: 2_000,
|
|
20
|
+
maxDelayMs: 30_000,
|
|
21
|
+
maxAttempts: 20,
|
|
22
|
+
jitterFactor: 0.3,
|
|
23
|
+
};
|
|
24
|
+
// ---- Fatal close codes ----
|
|
25
|
+
/** Auth errors — do not reconnect */
|
|
26
|
+
const FATAL_CLOSE_CODES = [4001];
|
|
27
|
+
const RPC_TIMEOUT_MS = 30_000;
|
|
28
|
+
// ---- Client ----
|
|
29
|
+
export class GatewayWsClient {
|
|
30
|
+
ws = null;
|
|
31
|
+
state = 'disconnected';
|
|
32
|
+
attempt = 0;
|
|
33
|
+
reconnectTimer = null;
|
|
34
|
+
staleWarnTimer = null;
|
|
35
|
+
staleReconnectTimer = null;
|
|
36
|
+
tickIntervalMs = 15_000; // Updated from hello-ok policy
|
|
37
|
+
pendingRpcs = new Map();
|
|
38
|
+
reconnectConfig;
|
|
39
|
+
destroyed = false;
|
|
40
|
+
/** Stored device token from hello-ok for future connects */
|
|
41
|
+
deviceToken = null;
|
|
42
|
+
/** Last hello-ok payload — contains initial snapshot */
|
|
43
|
+
lastHelloOk = null;
|
|
44
|
+
/** Event listeners */
|
|
45
|
+
listeners = new Map();
|
|
46
|
+
gatewayUrl;
|
|
47
|
+
gatewayToken;
|
|
48
|
+
constructor(config, reconnect) {
|
|
49
|
+
// Convert to WebSocket URL: http→ws, https→wss, bare host:port→ws://
|
|
50
|
+
let url = config.gatewayUrl.replace(/\/$/, '');
|
|
51
|
+
if (/^https:\/\//.test(url)) {
|
|
52
|
+
url = url.replace(/^https:\/\//, 'wss://');
|
|
53
|
+
}
|
|
54
|
+
else if (/^http:\/\//.test(url)) {
|
|
55
|
+
url = url.replace(/^http:\/\//, 'ws://');
|
|
56
|
+
}
|
|
57
|
+
else if (!/^wss?:\/\//.test(url)) {
|
|
58
|
+
url = `ws://${url}`;
|
|
59
|
+
}
|
|
60
|
+
this.gatewayUrl = url;
|
|
61
|
+
this.gatewayToken = config.gatewayToken;
|
|
62
|
+
this.reconnectConfig = { ...DEFAULT_RECONNECT, ...reconnect };
|
|
63
|
+
}
|
|
64
|
+
// ---- Public API ----
|
|
65
|
+
/** Start the WebSocket connection and handshake */
|
|
66
|
+
connect() {
|
|
67
|
+
if (this.destroyed)
|
|
68
|
+
return;
|
|
69
|
+
if (this.state === 'connected' || this.state === 'handshaking')
|
|
70
|
+
return;
|
|
71
|
+
this.setState(this.attempt === 0 ? 'connecting' : 'reconnecting');
|
|
72
|
+
const url = this.gatewayUrl;
|
|
73
|
+
logger.info(TAG, `Connecting to gateway (attempt ${this.attempt + 1}): ${url}`);
|
|
74
|
+
const ws = new WebSocket(url);
|
|
75
|
+
this.ws = ws;
|
|
76
|
+
ws.on('open', () => {
|
|
77
|
+
logger.info(TAG, 'WebSocket open, awaiting challenge');
|
|
78
|
+
this.setState('handshaking');
|
|
79
|
+
});
|
|
80
|
+
ws.on('message', (data) => {
|
|
81
|
+
this.handleMessage(data);
|
|
82
|
+
});
|
|
83
|
+
ws.on('close', (code, reason) => {
|
|
84
|
+
const reasonStr = reason.toString();
|
|
85
|
+
logger.info(TAG, `WebSocket closed: ${code} ${reasonStr}`);
|
|
86
|
+
this.ws = null;
|
|
87
|
+
this.clearStaleTimer();
|
|
88
|
+
this.rejectAllPending('Connection closed');
|
|
89
|
+
if (this.destroyed)
|
|
90
|
+
return;
|
|
91
|
+
const wasConnected = this.state === 'connected' || this.state === 'handshaking';
|
|
92
|
+
if (wasConnected) {
|
|
93
|
+
this.emit('disconnected', { code, reason: reasonStr });
|
|
94
|
+
}
|
|
95
|
+
// Fatal close codes: do not reconnect
|
|
96
|
+
if (FATAL_CLOSE_CODES.includes(code)) {
|
|
97
|
+
logger.error(TAG, `Fatal close code ${code} — authentication failed, not reconnecting`);
|
|
98
|
+
this.setState('failed');
|
|
99
|
+
this.emit('failed', { reason: `Authentication failed (code ${code})` });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.scheduleReconnect();
|
|
103
|
+
});
|
|
104
|
+
ws.on('error', (err) => {
|
|
105
|
+
logger.error(TAG, `WebSocket error: ${err.message}`);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Disconnect and clean up. Cannot reconnect after this. */
|
|
109
|
+
destroy() {
|
|
110
|
+
this.destroyed = true;
|
|
111
|
+
this.clearReconnectTimer();
|
|
112
|
+
this.clearStaleTimer();
|
|
113
|
+
this.rejectAllPending('Client destroyed');
|
|
114
|
+
if (this.ws) {
|
|
115
|
+
this.ws.close(1000, 'Client shutting down');
|
|
116
|
+
this.ws = null;
|
|
117
|
+
}
|
|
118
|
+
this.setState('disconnected');
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Send an RPC request and wait for the response.
|
|
122
|
+
* Used for: agent (chat), health, status
|
|
123
|
+
*/
|
|
124
|
+
async sendRpc(method, params) {
|
|
125
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
126
|
+
throw new Error(`Cannot send RPC: not connected (state=${this.state})`);
|
|
127
|
+
}
|
|
128
|
+
const id = `rpc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
129
|
+
const frame = {
|
|
130
|
+
type: 'req',
|
|
131
|
+
id,
|
|
132
|
+
method,
|
|
133
|
+
...(params && { params }),
|
|
134
|
+
};
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const timer = setTimeout(() => {
|
|
137
|
+
this.pendingRpcs.delete(id);
|
|
138
|
+
reject(new Error(`RPC ${method} timed out after ${RPC_TIMEOUT_MS}ms`));
|
|
139
|
+
}, RPC_TIMEOUT_MS);
|
|
140
|
+
this.pendingRpcs.set(id, { resolve, reject, timer });
|
|
141
|
+
this.ws.send(JSON.stringify(frame));
|
|
142
|
+
logger.debug(TAG, `Sent RPC: ${method} (id=${id})`);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/** Register an event listener */
|
|
146
|
+
on(event, listener) {
|
|
147
|
+
if (!this.listeners.has(event)) {
|
|
148
|
+
this.listeners.set(event, new Set());
|
|
149
|
+
}
|
|
150
|
+
this.listeners.get(event).add(listener);
|
|
151
|
+
}
|
|
152
|
+
/** Remove an event listener */
|
|
153
|
+
off(event, listener) {
|
|
154
|
+
this.listeners.get(event)?.delete(listener);
|
|
155
|
+
}
|
|
156
|
+
/** Get current connection state */
|
|
157
|
+
getState() {
|
|
158
|
+
return this.state;
|
|
159
|
+
}
|
|
160
|
+
/** Get the hello-ok snapshot from the last successful connection */
|
|
161
|
+
getHelloOk() {
|
|
162
|
+
return this.lastHelloOk;
|
|
163
|
+
}
|
|
164
|
+
/** Get stored device token (for future connects) */
|
|
165
|
+
getDeviceToken() {
|
|
166
|
+
return this.deviceToken;
|
|
167
|
+
}
|
|
168
|
+
// ---- Message handling ----
|
|
169
|
+
handleMessage(data) {
|
|
170
|
+
let frame;
|
|
171
|
+
try {
|
|
172
|
+
frame = JSON.parse(data.toString());
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
logger.warn(TAG, 'Received unparseable message');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// -- Response frames (hello-ok, RPC responses) --
|
|
179
|
+
if (frame.type === 'res') {
|
|
180
|
+
this.handleResponse(frame);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// -- Event frames --
|
|
184
|
+
if (frame.type === 'event') {
|
|
185
|
+
this.handleEvent(frame);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// -- Request frames from gateway (unexpected for us) --
|
|
189
|
+
if (frame.type === 'req') {
|
|
190
|
+
logger.debug(TAG, `Received request from gateway (unexpected): ${frame.id}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
handleResponse(frame) {
|
|
194
|
+
const id = frame.id;
|
|
195
|
+
if (!id)
|
|
196
|
+
return;
|
|
197
|
+
// Check pending RPCs
|
|
198
|
+
const pending = this.pendingRpcs.get(id);
|
|
199
|
+
if (pending) {
|
|
200
|
+
clearTimeout(pending.timer);
|
|
201
|
+
this.pendingRpcs.delete(id);
|
|
202
|
+
if (frame.ok) {
|
|
203
|
+
// Check if this is the hello-ok response to our connect request
|
|
204
|
+
if (frame.payload && typeof frame.payload === 'object') {
|
|
205
|
+
const payload = frame.payload;
|
|
206
|
+
if (payload.type === 'hello-ok') {
|
|
207
|
+
this.handleHelloOk(frame.payload);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
pending.resolve(frame.payload);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
pending.reject(new Error(frame.error?.message ?? `RPC failed (code ${frame.error?.code ?? 'unknown'})`));
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// Unexpected response
|
|
218
|
+
if (!frame.ok) {
|
|
219
|
+
logger.warn(TAG, `Unexpected error response (id=${id}): ${frame.error?.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
handleEvent(frame) {
|
|
223
|
+
const event = frame.event;
|
|
224
|
+
switch (event) {
|
|
225
|
+
case 'connect.challenge':
|
|
226
|
+
this.handleChallenge(frame.payload);
|
|
227
|
+
break;
|
|
228
|
+
case 'agent':
|
|
229
|
+
this.resetStaleTimer();
|
|
230
|
+
if (frame.payload && typeof frame.payload === 'object') {
|
|
231
|
+
const p = frame.payload;
|
|
232
|
+
this.emit('agent', {
|
|
233
|
+
runId: p.runId,
|
|
234
|
+
stream: p.stream,
|
|
235
|
+
data: p.data,
|
|
236
|
+
seq: frame.seq,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
case 'presence':
|
|
241
|
+
this.resetStaleTimer();
|
|
242
|
+
this.emit('presence', { payload: frame.payload, seq: frame.seq });
|
|
243
|
+
break;
|
|
244
|
+
case 'tick':
|
|
245
|
+
this.resetStaleTimer();
|
|
246
|
+
this.emit('tick', undefined);
|
|
247
|
+
break;
|
|
248
|
+
case 'shutdown':
|
|
249
|
+
logger.warn(TAG, 'Gateway shutting down — will reconnect');
|
|
250
|
+
this.emit('shutdown', undefined);
|
|
251
|
+
// Don't immediately reconnect; the close event will trigger reconnection
|
|
252
|
+
break;
|
|
253
|
+
default:
|
|
254
|
+
logger.debug(TAG, `Unknown gateway event: ${event}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// ---- Handshake ----
|
|
258
|
+
/**
|
|
259
|
+
* Step 1: Receive connect.challenge with {nonce, ts}.
|
|
260
|
+
* Step 2: Send connect request with auth token.
|
|
261
|
+
*/
|
|
262
|
+
handleChallenge(payload) {
|
|
263
|
+
logger.info(TAG, `Received challenge (nonce=${payload.nonce?.slice(0, 8)}...)`);
|
|
264
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
265
|
+
logger.error(TAG, 'Cannot respond to challenge: WebSocket not open');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const connectId = `connect-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
269
|
+
const connectFrame = {
|
|
270
|
+
type: 'req',
|
|
271
|
+
id: connectId,
|
|
272
|
+
method: 'connect',
|
|
273
|
+
params: {
|
|
274
|
+
minProtocol: MIN_PROTOCOL_VERSION,
|
|
275
|
+
maxProtocol: MAX_PROTOCOL_VERSION,
|
|
276
|
+
client: {
|
|
277
|
+
id: CLIENT_ID,
|
|
278
|
+
version: CLIENT_VERSION,
|
|
279
|
+
platform: 'node',
|
|
280
|
+
mode: 'backend',
|
|
281
|
+
},
|
|
282
|
+
role: 'operator',
|
|
283
|
+
scopes: ['operator.read', 'operator.write'],
|
|
284
|
+
auth: {
|
|
285
|
+
token: this.gatewayToken,
|
|
286
|
+
...(this.deviceToken && { deviceToken: this.deviceToken }),
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
// Register as pending RPC so the hello-ok response routes through handleResponse
|
|
291
|
+
const timer = setTimeout(() => {
|
|
292
|
+
this.pendingRpcs.delete(connectId);
|
|
293
|
+
logger.error(TAG, 'Connect handshake timed out');
|
|
294
|
+
// Force close to trigger reconnect
|
|
295
|
+
this.ws?.close(4000, 'Handshake timeout');
|
|
296
|
+
}, RPC_TIMEOUT_MS);
|
|
297
|
+
this.pendingRpcs.set(connectId, {
|
|
298
|
+
resolve: () => { },
|
|
299
|
+
reject: (err) => {
|
|
300
|
+
logger.error(TAG, `Handshake failed: ${err.message}`);
|
|
301
|
+
this.ws?.close(4000, 'Handshake rejected');
|
|
302
|
+
},
|
|
303
|
+
timer,
|
|
304
|
+
});
|
|
305
|
+
this.ws.send(JSON.stringify(connectFrame));
|
|
306
|
+
logger.info(TAG, 'Sent connect request');
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Step 3: Process hello-ok response.
|
|
310
|
+
* Extracts policy, snapshot, and device token.
|
|
311
|
+
*/
|
|
312
|
+
handleHelloOk(payload) {
|
|
313
|
+
logger.info(TAG, `Connected to gateway (protocol=${payload.protocol})`);
|
|
314
|
+
this.lastHelloOk = payload;
|
|
315
|
+
this.attempt = 0; // Reset reconnect counter
|
|
316
|
+
// Parse policy
|
|
317
|
+
if (payload.policy?.tickIntervalMs) {
|
|
318
|
+
this.tickIntervalMs = payload.policy.tickIntervalMs;
|
|
319
|
+
logger.debug(TAG, `Tick interval: ${this.tickIntervalMs}ms`);
|
|
320
|
+
}
|
|
321
|
+
// Store device token for future connects
|
|
322
|
+
if (payload.auth?.deviceToken) {
|
|
323
|
+
this.deviceToken = payload.auth.deviceToken;
|
|
324
|
+
logger.debug(TAG, 'Stored device token');
|
|
325
|
+
}
|
|
326
|
+
// Log snapshot info
|
|
327
|
+
const snap = payload.snapshot;
|
|
328
|
+
if (snap) {
|
|
329
|
+
logger.info(TAG, `Gateway uptime: ${Math.round((snap.uptimeMs ?? 0) / 1000)}s, stateVersion: ${JSON.stringify(snap.stateVersion)}`);
|
|
330
|
+
}
|
|
331
|
+
this.setState('connected');
|
|
332
|
+
this.resetStaleTimer();
|
|
333
|
+
this.emit('connected', payload);
|
|
334
|
+
}
|
|
335
|
+
// ---- Stale detection ----
|
|
336
|
+
/** Reset the stale timers — called on any incoming event/tick */
|
|
337
|
+
resetStaleTimer() {
|
|
338
|
+
this.clearStaleTimer();
|
|
339
|
+
const warnMs = this.tickIntervalMs * 2;
|
|
340
|
+
const reconnectMs = this.tickIntervalMs * 3;
|
|
341
|
+
// Warn at 2x tick interval
|
|
342
|
+
this.staleWarnTimer = setTimeout(() => {
|
|
343
|
+
if (this.state === 'connected') {
|
|
344
|
+
logger.warn(TAG, `No data received for ${warnMs}ms — connection may be stale`);
|
|
345
|
+
}
|
|
346
|
+
}, warnMs);
|
|
347
|
+
// Force reconnect at 3x tick interval
|
|
348
|
+
this.staleReconnectTimer = setTimeout(() => {
|
|
349
|
+
if (this.state === 'connected') {
|
|
350
|
+
logger.error(TAG, `No data received for ${reconnectMs}ms — forcing reconnect`);
|
|
351
|
+
this.ws?.close(4000, 'Stale connection');
|
|
352
|
+
}
|
|
353
|
+
}, reconnectMs);
|
|
354
|
+
}
|
|
355
|
+
clearStaleTimer() {
|
|
356
|
+
if (this.staleWarnTimer) {
|
|
357
|
+
clearTimeout(this.staleWarnTimer);
|
|
358
|
+
this.staleWarnTimer = null;
|
|
359
|
+
}
|
|
360
|
+
if (this.staleReconnectTimer) {
|
|
361
|
+
clearTimeout(this.staleReconnectTimer);
|
|
362
|
+
this.staleReconnectTimer = null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// ---- Reconnection ----
|
|
366
|
+
scheduleReconnect() {
|
|
367
|
+
this.attempt++;
|
|
368
|
+
if (this.attempt > this.reconnectConfig.maxAttempts) {
|
|
369
|
+
logger.error(TAG, `Max reconnection attempts (${this.reconnectConfig.maxAttempts}) exceeded`);
|
|
370
|
+
this.setState('failed');
|
|
371
|
+
this.emit('failed', { reason: 'Max reconnection attempts exceeded' });
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const delay = computeDelay(this.attempt - 1, this.reconnectConfig);
|
|
375
|
+
logger.info(TAG, `Reconnecting in ${Math.round(delay)}ms (attempt ${this.attempt}/${this.reconnectConfig.maxAttempts})`);
|
|
376
|
+
this.setState('reconnecting');
|
|
377
|
+
this.emit('reconnecting', { attempt: this.attempt, delayMs: Math.round(delay) });
|
|
378
|
+
this.reconnectTimer = setTimeout(() => {
|
|
379
|
+
this.reconnectTimer = null;
|
|
380
|
+
this.connect();
|
|
381
|
+
}, delay);
|
|
382
|
+
}
|
|
383
|
+
clearReconnectTimer() {
|
|
384
|
+
if (this.reconnectTimer) {
|
|
385
|
+
clearTimeout(this.reconnectTimer);
|
|
386
|
+
this.reconnectTimer = null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// ---- Internal helpers ----
|
|
390
|
+
setState(newState) {
|
|
391
|
+
if (this.state === newState)
|
|
392
|
+
return;
|
|
393
|
+
const prev = this.state;
|
|
394
|
+
this.state = newState;
|
|
395
|
+
logger.debug(TAG, `State: ${prev} → ${newState}`);
|
|
396
|
+
}
|
|
397
|
+
emit(event, payload) {
|
|
398
|
+
const listeners = this.listeners.get(event);
|
|
399
|
+
if (!listeners)
|
|
400
|
+
return;
|
|
401
|
+
for (const listener of listeners) {
|
|
402
|
+
try {
|
|
403
|
+
listener(payload);
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
logger.error(TAG, `Event listener error (${event}): ${err instanceof Error ? err.message : String(err)}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
rejectAllPending(reason) {
|
|
411
|
+
for (const [, pending] of this.pendingRpcs) {
|
|
412
|
+
clearTimeout(pending.timer);
|
|
413
|
+
pending.reject(new Error(reason));
|
|
414
|
+
}
|
|
415
|
+
this.pendingRpcs.clear();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { GatewayHttpClient } from './gateway-http-client.js';
|
|
2
|
+
import type { ToolMap } from './tool-discovery.js';
|
|
3
|
+
import type { TickerData, CandleData, OrderData, MarketStructureData, CryptoMetricsData, VolumeAnalysisData } from '../types.js';
|
|
4
|
+
export declare const TICKER_INTERVAL_MS = 5000;
|
|
5
|
+
export declare const OHLCV_INTERVAL_MS = 15000;
|
|
6
|
+
export declare const POSITIONS_INTERVAL_MS = 10000;
|
|
7
|
+
export declare const MARKET_STRUCTURE_INTERVAL_MS = 60000;
|
|
8
|
+
/** CCXT position — minimal shape needed by poller */
|
|
9
|
+
export interface CcxtPosition {
|
|
10
|
+
symbol: string;
|
|
11
|
+
side: 'long' | 'short' | string;
|
|
12
|
+
contracts?: number;
|
|
13
|
+
contractSize?: number;
|
|
14
|
+
entryPrice?: number;
|
|
15
|
+
markPrice?: number;
|
|
16
|
+
unrealizedPnl?: number;
|
|
17
|
+
leverage?: number;
|
|
18
|
+
notional?: number;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/** Callbacks fired by the Poller when fresh data arrives from polls. */
|
|
22
|
+
export interface PollerCallbacks {
|
|
23
|
+
/** Ticker data from fetch_ticker — fires every 1s */
|
|
24
|
+
onTicker: (data: TickerData) => void;
|
|
25
|
+
/** Candle data from fetch_ohlcv — fires every 5s */
|
|
26
|
+
onCandle: (data: CandleData) => void;
|
|
27
|
+
/** Raw CCXT positions from fetch_positions — fires every 3s */
|
|
28
|
+
onPositions: (positions: CcxtPosition[]) => void;
|
|
29
|
+
/** Parsed balance from fetch_balance — fires every 10s */
|
|
30
|
+
onBalance: (balance: {
|
|
31
|
+
currency: string;
|
|
32
|
+
total: number;
|
|
33
|
+
available: number;
|
|
34
|
+
locked: number;
|
|
35
|
+
}) => void;
|
|
36
|
+
/** Parsed open orders from fetch_open_orders — fires every 3s */
|
|
37
|
+
onOpenOrders: (orders: OrderData[]) => void;
|
|
38
|
+
/** Multi-TF analysis from get_market_structure — fires every 30s */
|
|
39
|
+
onMarketStructure?: (data: MarketStructureData) => void;
|
|
40
|
+
/** Crypto metrics (funding rate + OI) — fires every 60s */
|
|
41
|
+
onCryptoMetrics?: (data: CryptoMetricsData) => void;
|
|
42
|
+
/** Volume/thrust analysis — fires every 15s */
|
|
43
|
+
onVolumeAnalysis?: (data: VolumeAnalysisData) => void;
|
|
44
|
+
}
|
|
45
|
+
/** True when an error message carries a Binance rate-limit / IP-ban signal
|
|
46
|
+
* (418 ban, or 429 / -1003 "too many requests" soft limit). Poll methods that
|
|
47
|
+
* wrap their own try/catch MUST rethrow these so guardedExec's catch can arm
|
|
48
|
+
* the process-wide circuit-breaker (applyRateLimitBackoff). Swallowing them
|
|
49
|
+
* internally as "supplementary/non-fatal" silently defeats the ban gate: a
|
|
50
|
+
* fixed-cadence poller keeps hammering a banned IP, and every request during a
|
|
51
|
+
* 418 EXTENDS the ban (see docs/CLAUDE/binance-ratelimit.md). Kept in sync
|
|
52
|
+
* with the classification branches in applyRateLimitBackoff. */
|
|
53
|
+
export declare function isRateLimitShapedError(msg: string): boolean;
|
|
54
|
+
export declare class Poller {
|
|
55
|
+
private readonly http;
|
|
56
|
+
private readonly toolMap;
|
|
57
|
+
private readonly symbol;
|
|
58
|
+
private readonly callbacks;
|
|
59
|
+
private readonly quoteCurrency;
|
|
60
|
+
private running;
|
|
61
|
+
private paused;
|
|
62
|
+
private timers;
|
|
63
|
+
/** Prevents concurrent polls of the same type (if HTTP is slow) */
|
|
64
|
+
private readonly inflight;
|
|
65
|
+
/** Process-wide circuit-breaker. While `Date.now() < cooldownUntilMs`,
|
|
66
|
+
* `guardedExec` skips EVERY poll regardless of type. Set when any poll
|
|
67
|
+
* observes a Binance 418 (IP ban) or 429 (rate-limit) error.
|
|
68
|
+
*
|
|
69
|
+
* Why this exists: pre-2026-05-15 the poller had no ban awareness — on a
|
|
70
|
+
* 418 every one of ~8 pollers (ticker 1s, positions/orders 3s, …) kept
|
|
71
|
+
* firing on its original cadence straight through the ban, and since
|
|
72
|
+
* Binance EXTENDS an IP ban every time you hit it during the ban, the
|
|
73
|
+
* system never recovered on its own (self-perpetuating lockout that
|
|
74
|
+
* zeroed the dashboard + blinded the agent for the whole window). The
|
|
75
|
+
* breaker makes the whole poller go quiet until Binance's stated ban
|
|
76
|
+
* expiry (+slack), or an exponential window for soft 429s. */
|
|
77
|
+
private cooldownUntilMs;
|
|
78
|
+
/** Index into RATE_LIMIT_BACKOFF_MS for consecutive soft 429s. Reset to
|
|
79
|
+
* -1 (→ step 0 on first hit) after any clean poll. */
|
|
80
|
+
private rateLimitStep;
|
|
81
|
+
/** Throttles the "in cooldown, skipping" log to once per cooldown window
|
|
82
|
+
* so a 2-min ban doesn't emit hundreds of identical skip lines. */
|
|
83
|
+
private cooldownLogged;
|
|
84
|
+
/** Per-tool poll-attribution for the current 60s window (observability
|
|
85
|
+
* only). The skill is a SEPARATE process from the plugin but shares the
|
|
86
|
+
* Binance per-IP weight budget; the plugin's ban-gate weight summary
|
|
87
|
+
* cannot see skill polls. This is the skill-side mirror so the IP load
|
|
88
|
+
* split is provable, not guessed. */
|
|
89
|
+
private pollWindowStart;
|
|
90
|
+
private readonly pollCountByTool;
|
|
91
|
+
constructor(http: GatewayHttpClient, toolMap: ToolMap, symbol: string, callbacks: PollerCallbacks, quoteCurrency?: string);
|
|
92
|
+
/**
|
|
93
|
+
* Start all poll loops. Each tool is polled on its own interval.
|
|
94
|
+
* Tools not present in the ToolMap are silently skipped.
|
|
95
|
+
* Each poll fires immediately on start, then repeats on interval.
|
|
96
|
+
*/
|
|
97
|
+
start(): void;
|
|
98
|
+
/** Stop all poll loops and clean up timers. */
|
|
99
|
+
stop(): void;
|
|
100
|
+
/** Pause polling (timers keep ticking but polls are skipped). */
|
|
101
|
+
pause(): void;
|
|
102
|
+
/** Resume polling after a pause. */
|
|
103
|
+
resume(): void;
|
|
104
|
+
/**
|
|
105
|
+
* Schedule a poll loop for a logical tool, only if it was discovered.
|
|
106
|
+
* Fires immediately, then repeats on interval.
|
|
107
|
+
*/
|
|
108
|
+
private scheduleIfAvailable;
|
|
109
|
+
/**
|
|
110
|
+
* Execute a poll function with error handling and in-flight guard.
|
|
111
|
+
* - Skipped when paused or stopped (checked before AND after acquiring inflight slot)
|
|
112
|
+
* - Skipped if a previous poll of the same type is still in-flight
|
|
113
|
+
* - Errors are logged and swallowed (never crash the provider)
|
|
114
|
+
*/
|
|
115
|
+
private guardedExec;
|
|
116
|
+
/** Inspect a failed poll's error for Binance rate-limit signals and arm
|
|
117
|
+
* the process-wide circuit-breaker accordingly.
|
|
118
|
+
*
|
|
119
|
+
* - **418** (IP ban): Binance returns `banned until <epoch-ms>` in the
|
|
120
|
+
* message. We respect that exact timestamp + slack — guessing a
|
|
121
|
+
* shorter window would just re-ban and extend it.
|
|
122
|
+
* - **429 / -1003 "Too many requests"** (soft rate-limit, no ban yet):
|
|
123
|
+
* exponential backoff so we stop climbing toward a 418.
|
|
124
|
+
*
|
|
125
|
+
* Any non-rate-limit error is left alone (normal transient handling). */
|
|
126
|
+
private applyRateLimitBackoff;
|
|
127
|
+
private pollTicker;
|
|
128
|
+
private pollOhlcv;
|
|
129
|
+
private pollPositions;
|
|
130
|
+
private pollBalance;
|
|
131
|
+
private pollOpenOrders;
|
|
132
|
+
private pollMarketStructure;
|
|
133
|
+
/**
|
|
134
|
+
* Fetch funding rate and open interest via the dedicated get_crypto_metrics tool.
|
|
135
|
+
* The plugin tool handles spot→perp symbol conversion and raw CCXT ticker parsing.
|
|
136
|
+
*/
|
|
137
|
+
private pollCryptoMetrics;
|
|
138
|
+
/**
|
|
139
|
+
* Fetch 25 bars of 5m OHLCV and compute volume analysis metrics:
|
|
140
|
+
* - Volume expansion ratio (current vs 20-bar avg)
|
|
141
|
+
* - Close position (where close sits in bar's range, 0=low, 1=high)
|
|
142
|
+
* - Directional thrust (volume-weighted close position, -1 to +1)
|
|
143
|
+
* - Buy volume estimate (proxy using close position)
|
|
144
|
+
*/
|
|
145
|
+
private pollVolumeAnalysis;
|
|
146
|
+
}
|