@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,102 @@
|
|
|
1
|
+
/** Role of a clientOrderId within a bracket. */
|
|
2
|
+
export type BracketRole = 'entry' | 'stop' | 'target';
|
|
3
|
+
/** Short tag stored inside clientOrderId suffix. */
|
|
4
|
+
export type BracketRoleTag = 'e' | 's' | 't';
|
|
5
|
+
/** Immutable opaque identifier tying entry + SL + TP together. */
|
|
6
|
+
export type BracketId = string;
|
|
7
|
+
/** Lifecycle state of a bracket attached to an open position. */
|
|
8
|
+
export type BracketState = 'pending_entry' | 'attaching' | 'active' | 'partial' | 'triggered_sl' | 'triggered_tp' | 'cancelled' | 'failed';
|
|
9
|
+
/** Operator-provided bracket parameters for a new entry. */
|
|
10
|
+
export interface BracketRequest {
|
|
11
|
+
symbol: string;
|
|
12
|
+
side: 'buy' | 'sell';
|
|
13
|
+
stopPrice?: number;
|
|
14
|
+
targetPrice?: number;
|
|
15
|
+
}
|
|
16
|
+
/** A single ledger entry representing one bracket. Authoritative on disk;
|
|
17
|
+
* mirrored to intelligence DB for durability. */
|
|
18
|
+
export interface BracketLedgerEntry {
|
|
19
|
+
bracketId: BracketId;
|
|
20
|
+
symbol: string;
|
|
21
|
+
entrySide: 'buy' | 'sell';
|
|
22
|
+
entryCid: string;
|
|
23
|
+
slCid?: string;
|
|
24
|
+
tpCid?: string;
|
|
25
|
+
stopPrice?: number;
|
|
26
|
+
targetPrice?: number;
|
|
27
|
+
/** Filled entry quantity the bracket legs were sized to (reduceOnly +
|
|
28
|
+
* explicit amount, NOT closePosition). modifyStop/modifyTarget MUST reuse
|
|
29
|
+
* this — rebuilding a leg with a placeholder size silently under-protects
|
|
30
|
+
* the position to that size. Set at attach time. */
|
|
31
|
+
qty?: number;
|
|
32
|
+
state: BracketState;
|
|
33
|
+
attachStartedAt?: string;
|
|
34
|
+
attachCompletedAt?: string;
|
|
35
|
+
attachLatencyMs?: number;
|
|
36
|
+
closedAt?: string;
|
|
37
|
+
closeReason?: 'triggered_sl' | 'triggered_tp' | 'cancelled_manual' | 'cancelled_auto' | 'attach_failed';
|
|
38
|
+
lastError?: string;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
}
|
|
42
|
+
/** Event emitted by BracketManager for telemetry + logging. */
|
|
43
|
+
export type BracketEvent = {
|
|
44
|
+
type: 'bracket.entry_submitted';
|
|
45
|
+
bracketId: BracketId;
|
|
46
|
+
symbol: string;
|
|
47
|
+
ts: string;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'bracket.entry_filled';
|
|
50
|
+
bracketId: BracketId;
|
|
51
|
+
symbol: string;
|
|
52
|
+
ts: string;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'bracket.attached';
|
|
55
|
+
bracketId: BracketId;
|
|
56
|
+
symbol: string;
|
|
57
|
+
latencyMs: number;
|
|
58
|
+
ts: string;
|
|
59
|
+
} | {
|
|
60
|
+
type: 'bracket.attach_failed';
|
|
61
|
+
bracketId: BracketId;
|
|
62
|
+
symbol: string;
|
|
63
|
+
error: string;
|
|
64
|
+
retryCount: number;
|
|
65
|
+
ts: string;
|
|
66
|
+
} | {
|
|
67
|
+
type: 'bracket.sla_breach';
|
|
68
|
+
bracketId: BracketId;
|
|
69
|
+
symbol: string;
|
|
70
|
+
latencyMs: number;
|
|
71
|
+
ts: string;
|
|
72
|
+
} | {
|
|
73
|
+
type: 'bracket.triggered';
|
|
74
|
+
bracketId: BracketId;
|
|
75
|
+
symbol: string;
|
|
76
|
+
side: 'sl' | 'tp';
|
|
77
|
+
ts: string;
|
|
78
|
+
} | {
|
|
79
|
+
type: 'bracket.cancelled';
|
|
80
|
+
bracketId: BracketId;
|
|
81
|
+
symbol: string;
|
|
82
|
+
reason: string;
|
|
83
|
+
ts: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: 'bracket.orphan_detected';
|
|
86
|
+
bracketId: BracketId;
|
|
87
|
+
symbol: string;
|
|
88
|
+
ts: string;
|
|
89
|
+
} | {
|
|
90
|
+
type: 'bracket.orphan_cancelled';
|
|
91
|
+
bracketId: BracketId;
|
|
92
|
+
symbol: string;
|
|
93
|
+
ts: string;
|
|
94
|
+
} | {
|
|
95
|
+
type: 'bracket.modified';
|
|
96
|
+
bracketId: BracketId;
|
|
97
|
+
symbol: string;
|
|
98
|
+
field: 'stop' | 'target';
|
|
99
|
+
oldValue: number;
|
|
100
|
+
newValue: number;
|
|
101
|
+
ts: string;
|
|
102
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Bracket order types — shared across BracketManager, BracketLedger,
|
|
2
|
+
// BracketReconciler, and the telemetry pusher.
|
|
3
|
+
//
|
|
4
|
+
// A "bracket" is the pair of exchange-side protective orders attached to a
|
|
5
|
+
// live position: a STOP_MARKET (stop-loss) and a TAKE_PROFIT_MARKET
|
|
6
|
+
// (take-profit), both reduceOnly + closePosition. One entry → one bracketId
|
|
7
|
+
// → three clientOrderIds (entry/stop/target).
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { BinancePrivateApi } from '../ccxt/binance-private.js';
|
|
2
|
+
interface TransferFetcher {
|
|
3
|
+
fetchTransfers(startMs: number, endMs: number): Promise<Array<{
|
|
4
|
+
tranId: string;
|
|
5
|
+
time: number;
|
|
6
|
+
amount: number;
|
|
7
|
+
asset: string;
|
|
8
|
+
}>>;
|
|
9
|
+
}
|
|
10
|
+
export interface DepositTrackerOptions {
|
|
11
|
+
/** Earliest transfer time to consider (ms). Defaults to `Date.now()`. */
|
|
12
|
+
sessionStartMs?: number;
|
|
13
|
+
/** Minimum gap between polls (ms). Transfers (deposits/withdrawals/spot↔
|
|
14
|
+
* futures moves) are RARE and only affect the cosmetic Day-P&L anchor —
|
|
15
|
+
* never a safety path. `fetchTransfers` is 30 Binance weight; at the old
|
|
16
|
+
* 30s default that was ~60 weight/min PERMANENT on an IP budget already
|
|
17
|
+
* pinned at the pacer ceiling 24/7 (2026-05-16 RCA). 5 min cuts it to
|
|
18
|
+
* ~6 weight/min; the only cost is a capital flow taking ≤5 min to be
|
|
19
|
+
* subtracted from Day P&L. Default: 5 min. */
|
|
20
|
+
minPollIntervalMs?: number;
|
|
21
|
+
/** Currencies to sum as quote (1:1 with USD assumed). Anything else is
|
|
22
|
+
* logged and ignored. */
|
|
23
|
+
quoteCurrencies?: readonly string[];
|
|
24
|
+
/** Injectable clock for tests. */
|
|
25
|
+
now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
export declare class DepositTracker {
|
|
28
|
+
private api;
|
|
29
|
+
private seenTranIds;
|
|
30
|
+
/** Last SUCCESSFUL poll — anchors the fetch window so transfers that land
|
|
31
|
+
* between a success and a run of failures are still covered by the next
|
|
32
|
+
* successful poll. */
|
|
33
|
+
private lastPollMs;
|
|
34
|
+
/** Last poll ATTEMPT (success or failure) — drives the throttle. Advancing
|
|
35
|
+
* this on failure too is what stops a sustained income-endpoint outage
|
|
36
|
+
* from retrying the 30-weight call on every balance cycle (~seconds
|
|
37
|
+
* apart) instead of once per minPollIntervalMs. */
|
|
38
|
+
private lastAttemptMs;
|
|
39
|
+
private sessionStartMs;
|
|
40
|
+
private netSinceSessionStart;
|
|
41
|
+
private readonly minPollIntervalMs;
|
|
42
|
+
private readonly quoteCurrencies;
|
|
43
|
+
private readonly now;
|
|
44
|
+
constructor(api: TransferFetcher | BinancePrivateApi, opts?: DepositTrackerOptions);
|
|
45
|
+
/** Reset the tracker — used when the agent re-anchors the session NAV for
|
|
46
|
+
* any reason (date rollover, operator-triggered reset). */
|
|
47
|
+
reset(sessionStartMs?: number): void;
|
|
48
|
+
getSessionStartMs(): number;
|
|
49
|
+
/** Net quote-currency transfers into (positive) or out of (negative) the
|
|
50
|
+
* futures wallet since the tracker was armed. */
|
|
51
|
+
getNetTransfers(): number;
|
|
52
|
+
/**
|
|
53
|
+
* Check the exchange for new transfers. Returns the delta (signed) that
|
|
54
|
+
* happened since the last poll — callers should add this to their session
|
|
55
|
+
* NAV anchor so the new wallet level doesn't register as trading P&L.
|
|
56
|
+
*
|
|
57
|
+
* The fetch is throttled by `minPollIntervalMs`; rapid calls return 0 without
|
|
58
|
+
* hitting the API.
|
|
59
|
+
*/
|
|
60
|
+
refresh(): Promise<number>;
|
|
61
|
+
}
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// DepositTracker — detects wallet-transfer deltas (spot↔futures moves,
|
|
2
|
+
// deposits, withdrawals) and feeds them to the enricher so capital flows
|
|
3
|
+
// are subtracted from Day P&L instead of being mistaken for trading gains.
|
|
4
|
+
import { logger } from '../logger.js';
|
|
5
|
+
const TAG = 'deposit-tracker';
|
|
6
|
+
export class DepositTracker {
|
|
7
|
+
api;
|
|
8
|
+
seenTranIds = new Set();
|
|
9
|
+
/** Last SUCCESSFUL poll — anchors the fetch window so transfers that land
|
|
10
|
+
* between a success and a run of failures are still covered by the next
|
|
11
|
+
* successful poll. */
|
|
12
|
+
lastPollMs;
|
|
13
|
+
/** Last poll ATTEMPT (success or failure) — drives the throttle. Advancing
|
|
14
|
+
* this on failure too is what stops a sustained income-endpoint outage
|
|
15
|
+
* from retrying the 30-weight call on every balance cycle (~seconds
|
|
16
|
+
* apart) instead of once per minPollIntervalMs. */
|
|
17
|
+
lastAttemptMs;
|
|
18
|
+
sessionStartMs;
|
|
19
|
+
netSinceSessionStart = 0;
|
|
20
|
+
minPollIntervalMs;
|
|
21
|
+
quoteCurrencies;
|
|
22
|
+
now;
|
|
23
|
+
constructor(api, opts = {}) {
|
|
24
|
+
this.api = api;
|
|
25
|
+
this.now = opts.now ?? (() => Date.now());
|
|
26
|
+
this.sessionStartMs = opts.sessionStartMs ?? this.now();
|
|
27
|
+
this.lastPollMs = this.sessionStartMs;
|
|
28
|
+
this.lastAttemptMs = this.sessionStartMs;
|
|
29
|
+
this.minPollIntervalMs = opts.minPollIntervalMs ?? 300_000;
|
|
30
|
+
this.quoteCurrencies = opts.quoteCurrencies ?? ['USDT', 'USDC'];
|
|
31
|
+
}
|
|
32
|
+
/** Reset the tracker — used when the agent re-anchors the session NAV for
|
|
33
|
+
* any reason (date rollover, operator-triggered reset). */
|
|
34
|
+
reset(sessionStartMs) {
|
|
35
|
+
this.seenTranIds.clear();
|
|
36
|
+
this.sessionStartMs = sessionStartMs ?? this.now();
|
|
37
|
+
this.lastPollMs = this.sessionStartMs;
|
|
38
|
+
this.lastAttemptMs = this.sessionStartMs;
|
|
39
|
+
this.netSinceSessionStart = 0;
|
|
40
|
+
}
|
|
41
|
+
getSessionStartMs() {
|
|
42
|
+
return this.sessionStartMs;
|
|
43
|
+
}
|
|
44
|
+
/** Net quote-currency transfers into (positive) or out of (negative) the
|
|
45
|
+
* futures wallet since the tracker was armed. */
|
|
46
|
+
getNetTransfers() {
|
|
47
|
+
return this.netSinceSessionStart;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check the exchange for new transfers. Returns the delta (signed) that
|
|
51
|
+
* happened since the last poll — callers should add this to their session
|
|
52
|
+
* NAV anchor so the new wallet level doesn't register as trading P&L.
|
|
53
|
+
*
|
|
54
|
+
* The fetch is throttled by `minPollIntervalMs`; rapid calls return 0 without
|
|
55
|
+
* hitting the API.
|
|
56
|
+
*/
|
|
57
|
+
async refresh() {
|
|
58
|
+
const now = this.now();
|
|
59
|
+
// Throttle on the last ATTEMPT (not the last success) — otherwise a
|
|
60
|
+
// sustained fetch failure bypasses the throttle entirely and every
|
|
61
|
+
// balance cycle re-fires the 30-weight income call into the outage.
|
|
62
|
+
if (now - this.lastAttemptMs < this.minPollIntervalMs)
|
|
63
|
+
return 0;
|
|
64
|
+
this.lastAttemptMs = now;
|
|
65
|
+
// A small lookback overlap guards against clock skew between our host and
|
|
66
|
+
// Binance's edge — duplicate entries get filtered via seenTranIds. The
|
|
67
|
+
// window anchors on the last SUCCESS so transfers that landed during a
|
|
68
|
+
// run of failed polls are still picked up.
|
|
69
|
+
const start = Math.max(this.sessionStartMs, this.lastPollMs - 5_000);
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
entries = await this.api.fetchTransfers(start, now);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
logger.warn(TAG, `Transfer poll failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
this.lastPollMs = now;
|
|
79
|
+
let delta = 0;
|
|
80
|
+
for (const e of entries) {
|
|
81
|
+
if (!e.tranId || this.seenTranIds.has(e.tranId))
|
|
82
|
+
continue;
|
|
83
|
+
this.seenTranIds.add(e.tranId);
|
|
84
|
+
if (this.quoteCurrencies.includes(e.asset)) {
|
|
85
|
+
delta += e.amount;
|
|
86
|
+
}
|
|
87
|
+
else if (e.amount !== 0) {
|
|
88
|
+
logger.warn(TAG, `Non-quote transfer ignored: ${e.amount} ${e.asset} (tranId=${e.tranId})`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (delta !== 0) {
|
|
92
|
+
this.netSinceSessionStart += delta;
|
|
93
|
+
logger.info(TAG, `Capital flow detected: ${delta > 0 ? '+' : ''}${delta.toFixed(4)} (session total: ${this.netSinceSessionStart.toFixed(4)})`);
|
|
94
|
+
}
|
|
95
|
+
return delta;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
3
|
+
export interface EmergencyStep {
|
|
4
|
+
description: string;
|
|
5
|
+
status: 'pending' | 'done' | 'failed';
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EmergencyProgress {
|
|
9
|
+
action: 'kill' | 'flatten';
|
|
10
|
+
status: 'in_progress' | 'completed' | 'failed' | 'partial';
|
|
11
|
+
steps: EmergencyStep[];
|
|
12
|
+
startedAt: string;
|
|
13
|
+
completedAt?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class EmergencyControls extends EventEmitter {
|
|
16
|
+
private adapter;
|
|
17
|
+
private isHedgeMode;
|
|
18
|
+
constructor(adapter: IExchangeAdapter, isHedgeMode?: boolean);
|
|
19
|
+
/**
|
|
20
|
+
* Kill switch: cancel all open orders on exchange.
|
|
21
|
+
* Retries up to 3 times on transient failure.
|
|
22
|
+
*/
|
|
23
|
+
kill(): Promise<EmergencyProgress>;
|
|
24
|
+
/**
|
|
25
|
+
* Flatten: close all positions at market.
|
|
26
|
+
* Loop-verifies: re-fetches positions after close, retries remainder.
|
|
27
|
+
* Uses reduceOnly to prevent accidental position reversal.
|
|
28
|
+
*/
|
|
29
|
+
flatten(): Promise<EmergencyProgress>;
|
|
30
|
+
private closePositionWithRetry;
|
|
31
|
+
private emitProgress;
|
|
32
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Emergency controls for live trading — kill switch and flatten with retry logic.
|
|
2
|
+
// These NEVER have a readiness gate — they must work in ALL adapter states.
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { logger, formatError } from '../logger.js';
|
|
5
|
+
const TAG = 'emergency';
|
|
6
|
+
const MAX_RETRIES = 3;
|
|
7
|
+
const RETRY_DELAY_MS = 1_000;
|
|
8
|
+
const MAX_FLATTEN_ATTEMPTS = 3;
|
|
9
|
+
export class EmergencyControls extends EventEmitter {
|
|
10
|
+
adapter;
|
|
11
|
+
isHedgeMode;
|
|
12
|
+
constructor(adapter, isHedgeMode = false) {
|
|
13
|
+
super();
|
|
14
|
+
this.adapter = adapter;
|
|
15
|
+
this.isHedgeMode = isHedgeMode;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Kill switch: cancel all open orders on exchange.
|
|
19
|
+
* Retries up to 3 times on transient failure.
|
|
20
|
+
*/
|
|
21
|
+
async kill() {
|
|
22
|
+
const progress = {
|
|
23
|
+
action: 'kill',
|
|
24
|
+
status: 'in_progress',
|
|
25
|
+
steps: [],
|
|
26
|
+
startedAt: new Date().toISOString(),
|
|
27
|
+
};
|
|
28
|
+
this.emitProgress(progress);
|
|
29
|
+
logger.warn(TAG, '🔴 KILL SWITCH ACTIVATED');
|
|
30
|
+
try {
|
|
31
|
+
// Fetch current open orders
|
|
32
|
+
const openOrders = await this.adapter.getOpenOrders();
|
|
33
|
+
if (openOrders.length === 0) {
|
|
34
|
+
progress.steps.push({ description: 'No open orders to cancel', status: 'done' });
|
|
35
|
+
progress.status = 'completed';
|
|
36
|
+
progress.completedAt = new Date().toISOString();
|
|
37
|
+
this.emitProgress(progress);
|
|
38
|
+
logger.info(TAG, 'Kill: no open orders');
|
|
39
|
+
return progress;
|
|
40
|
+
}
|
|
41
|
+
progress.steps.push({ description: `Cancelling ${openOrders.length} orders...`, status: 'pending' });
|
|
42
|
+
this.emitProgress(progress);
|
|
43
|
+
// Cancel all with retry
|
|
44
|
+
let success = false;
|
|
45
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
46
|
+
try {
|
|
47
|
+
await this.adapter.cancelAllOrders();
|
|
48
|
+
success = true;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.warn(TAG, `Kill attempt ${attempt}/${MAX_RETRIES} failed: ${formatError(err)}`);
|
|
53
|
+
if (attempt < MAX_RETRIES) {
|
|
54
|
+
await sleep(RETRY_DELAY_MS);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (success) {
|
|
59
|
+
progress.steps[progress.steps.length - 1].status = 'done';
|
|
60
|
+
progress.steps[progress.steps.length - 1].description = `${openOrders.length} orders cancelled`;
|
|
61
|
+
progress.status = 'completed';
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
progress.steps[progress.steps.length - 1].status = 'failed';
|
|
65
|
+
progress.steps[progress.steps.length - 1].error = 'Failed after 3 retries';
|
|
66
|
+
progress.steps.push({
|
|
67
|
+
description: 'MANUAL INTERVENTION REQUIRED — cancel orders manually on Binance',
|
|
68
|
+
status: 'failed',
|
|
69
|
+
});
|
|
70
|
+
progress.status = 'failed';
|
|
71
|
+
}
|
|
72
|
+
progress.completedAt = new Date().toISOString();
|
|
73
|
+
this.emitProgress(progress);
|
|
74
|
+
return progress;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
logger.error(TAG, `Kill switch error: ${formatError(err)}`);
|
|
78
|
+
progress.status = 'failed';
|
|
79
|
+
progress.steps.push({
|
|
80
|
+
description: `MANUAL INTERVENTION REQUIRED: ${formatError(err)}`,
|
|
81
|
+
status: 'failed',
|
|
82
|
+
error: formatError(err),
|
|
83
|
+
});
|
|
84
|
+
progress.completedAt = new Date().toISOString();
|
|
85
|
+
this.emitProgress(progress);
|
|
86
|
+
return progress;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Flatten: close all positions at market.
|
|
91
|
+
* Loop-verifies: re-fetches positions after close, retries remainder.
|
|
92
|
+
* Uses reduceOnly to prevent accidental position reversal.
|
|
93
|
+
*/
|
|
94
|
+
async flatten() {
|
|
95
|
+
const progress = {
|
|
96
|
+
action: 'flatten',
|
|
97
|
+
status: 'in_progress',
|
|
98
|
+
steps: [],
|
|
99
|
+
startedAt: new Date().toISOString(),
|
|
100
|
+
};
|
|
101
|
+
this.emitProgress(progress);
|
|
102
|
+
logger.warn(TAG, '🟠 FLATTEN ALL ACTIVATED');
|
|
103
|
+
try {
|
|
104
|
+
for (let attempt = 1; attempt <= MAX_FLATTEN_ATTEMPTS; attempt++) {
|
|
105
|
+
// Fresh snapshot of positions from exchange. getPositionsOrNull()
|
|
106
|
+
// returns null when the fetch FAILED (429 / weight-paced / transient)
|
|
107
|
+
// — position state is UNKNOWN, NOT "flat". Treating a failed fetch as
|
|
108
|
+
// "all closed" would make the single most safety-critical control
|
|
109
|
+
// report a green "Flatten complete" to the operator mid-crisis while
|
|
110
|
+
// positions are still live on Binance. Never declare success on null;
|
|
111
|
+
// skip this attempt's close work and retry (final verification below
|
|
112
|
+
// delivers an honest partial/MANUAL-INTERVENTION verdict if it
|
|
113
|
+
// persists). Same null≠empty contract as closePosition/reconciler.
|
|
114
|
+
const positions = await this.adapter.getPositionsOrNull();
|
|
115
|
+
if (positions === null) {
|
|
116
|
+
logger.warn(TAG, `Flatten attempt ${attempt}/${MAX_FLATTEN_ATTEMPTS}: position fetch failed (exchange data unavailable) — cannot confirm flat, retrying`);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (positions.length === 0) {
|
|
120
|
+
progress.steps.push({ description: 'All positions closed', status: 'done' });
|
|
121
|
+
progress.status = 'completed';
|
|
122
|
+
progress.completedAt = new Date().toISOString();
|
|
123
|
+
this.emitProgress(progress);
|
|
124
|
+
logger.info(TAG, 'Flatten complete — no positions remaining');
|
|
125
|
+
return progress;
|
|
126
|
+
}
|
|
127
|
+
if (attempt > 1) {
|
|
128
|
+
logger.warn(TAG, `Flatten retry ${attempt}/${MAX_FLATTEN_ATTEMPTS}: ${positions.length} positions still open`);
|
|
129
|
+
}
|
|
130
|
+
// Close each position
|
|
131
|
+
for (const position of positions) {
|
|
132
|
+
const step = {
|
|
133
|
+
description: `Closing ${position.symbol} ${position.side} ${position.contracts}`,
|
|
134
|
+
status: 'pending',
|
|
135
|
+
};
|
|
136
|
+
progress.steps.push(step);
|
|
137
|
+
this.emitProgress(progress);
|
|
138
|
+
try {
|
|
139
|
+
await this.closePositionWithRetry(position);
|
|
140
|
+
step.status = 'done';
|
|
141
|
+
step.description = `Closed ${position.symbol} ${position.side} ${position.contracts}`;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
step.status = 'failed';
|
|
145
|
+
step.error = formatError(err);
|
|
146
|
+
logger.error(TAG, `Failed to close ${position.symbol}: ${formatError(err)}`);
|
|
147
|
+
}
|
|
148
|
+
this.emitProgress(progress);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Final verification. A null here means we exhausted all attempts
|
|
152
|
+
// without ever getting a trustworthy snapshot — we CANNOT confirm the
|
|
153
|
+
// account is flat, so we must NOT report `completed`. Surface it as
|
|
154
|
+
// partial + MANUAL INTERVENTION (operator must check Binance directly)
|
|
155
|
+
// rather than the false-green that motivated this fix.
|
|
156
|
+
const remaining = await this.adapter.getPositionsOrNull();
|
|
157
|
+
if (remaining === null) {
|
|
158
|
+
progress.status = 'partial';
|
|
159
|
+
progress.steps.push({
|
|
160
|
+
description: 'MANUAL INTERVENTION REQUIRED — could not verify positions are closed (exchange data unavailable); check Binance directly',
|
|
161
|
+
status: 'failed',
|
|
162
|
+
});
|
|
163
|
+
logger.error(TAG, 'Flatten verification failed: position fetch unavailable — flat state UNCONFIRMED');
|
|
164
|
+
}
|
|
165
|
+
else if (remaining.length > 0) {
|
|
166
|
+
progress.status = 'partial';
|
|
167
|
+
progress.steps.push({
|
|
168
|
+
description: `MANUAL INTERVENTION REQUIRED — ${remaining.length} positions still open on Binance`,
|
|
169
|
+
status: 'failed',
|
|
170
|
+
});
|
|
171
|
+
logger.error(TAG, `Flatten incomplete: ${remaining.length} positions remain`);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
progress.status = 'completed';
|
|
175
|
+
}
|
|
176
|
+
progress.completedAt = new Date().toISOString();
|
|
177
|
+
this.emitProgress(progress);
|
|
178
|
+
return progress;
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
logger.error(TAG, `Flatten error: ${formatError(err)}`);
|
|
182
|
+
progress.status = 'failed';
|
|
183
|
+
progress.steps.push({
|
|
184
|
+
description: `MANUAL INTERVENTION REQUIRED: ${formatError(err)}`,
|
|
185
|
+
status: 'failed',
|
|
186
|
+
error: formatError(err),
|
|
187
|
+
});
|
|
188
|
+
progress.completedAt = new Date().toISOString();
|
|
189
|
+
this.emitProgress(progress);
|
|
190
|
+
return progress;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// ---- Internal ----
|
|
194
|
+
async closePositionWithRetry(position) {
|
|
195
|
+
const closeSide = position.side === 'long' ? 'sell' : 'buy';
|
|
196
|
+
const options = { emergency: true };
|
|
197
|
+
if (this.isHedgeMode) {
|
|
198
|
+
options.positionSide = position.side === 'long' ? 'LONG' : 'SHORT';
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
options.reduceOnly = true;
|
|
202
|
+
}
|
|
203
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
204
|
+
try {
|
|
205
|
+
return await this.adapter.createOrder(position.symbol, closeSide, 'market', position.contracts, undefined, undefined, options);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
logger.warn(TAG, `Close ${position.symbol} attempt ${attempt}/${MAX_RETRIES}: ${formatError(err)}`);
|
|
209
|
+
if (attempt < MAX_RETRIES) {
|
|
210
|
+
await sleep(RETRY_DELAY_MS);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// TypeScript: unreachable, but satisfies return type
|
|
218
|
+
throw new Error(`Failed to close ${position.symbol} after ${MAX_RETRIES} attempts`);
|
|
219
|
+
}
|
|
220
|
+
emitProgress(progress) {
|
|
221
|
+
this.emit('progress', progress);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function sleep(ms) {
|
|
225
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
226
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type ErrorAction = 'report' | 'wait_and_retry' | 'check_and_retry' | 'fix_precision' | 'sync_time';
|
|
2
|
+
export interface ParsedExchangeError {
|
|
3
|
+
code: number | null;
|
|
4
|
+
message: string;
|
|
5
|
+
action: ErrorAction;
|
|
6
|
+
retryAfterMs: number;
|
|
7
|
+
isRetryable: boolean;
|
|
8
|
+
isNetworkError: boolean;
|
|
9
|
+
humanMessage: string;
|
|
10
|
+
}
|
|
11
|
+
/** Parse a CCXT/Binance error into a structured response. */
|
|
12
|
+
export declare function parseExchangeError(err: unknown): ParsedExchangeError;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Exchange error handler — maps Binance error codes to human-readable messages
|
|
2
|
+
// and determines appropriate recovery action.
|
|
3
|
+
import { logger } from '../logger.js';
|
|
4
|
+
const TAG = 'exchange-error';
|
|
5
|
+
/** Known Binance Futures error codes and their human-readable meanings. */
|
|
6
|
+
const BINANCE_ERRORS = {
|
|
7
|
+
// Order errors
|
|
8
|
+
[-2019]: { message: 'Insufficient margin', action: 'report' },
|
|
9
|
+
[-1111]: { message: 'Precision error (lot size or tick size mismatch)', action: 'fix_precision' },
|
|
10
|
+
[-4131]: { message: 'Order below minimum notional value', action: 'report' },
|
|
11
|
+
[-1015]: { message: 'Too many orders — exchange rate limit hit', action: 'wait_and_retry' },
|
|
12
|
+
[-2022]: { message: 'Stop price invalid (must be above/below current price depending on side)', action: 'report' },
|
|
13
|
+
[-1021]: { message: 'Timestamp sync error — clock drift', action: 'sync_time' },
|
|
14
|
+
[-1003]: { message: 'IP rate limit exceeded', action: 'wait_and_retry' },
|
|
15
|
+
[-1010]: { message: 'Order not found', action: 'report' },
|
|
16
|
+
[-2021]: { message: 'Order would immediately trigger', action: 'report' },
|
|
17
|
+
[-4164]: { message: 'Order price exceeds MAX_PRICE filter', action: 'report' },
|
|
18
|
+
[-4165]: { message: 'Order price below MIN_PRICE filter', action: 'report' },
|
|
19
|
+
[-4028]: { message: 'Reduce only rejected — no open position', action: 'report' },
|
|
20
|
+
[-4005]: { message: 'Order quantity too small', action: 'report' },
|
|
21
|
+
[-4003]: { message: 'Order quantity exceeds MAX_QTY', action: 'report' },
|
|
22
|
+
[-4014]: { message: 'Too many open orders', action: 'report' },
|
|
23
|
+
// System errors
|
|
24
|
+
[-1000]: { message: 'Unknown exchange error', action: 'report' },
|
|
25
|
+
[-1001]: { message: 'Exchange internal error — try again later', action: 'wait_and_retry' },
|
|
26
|
+
[-1002]: { message: 'Unauthorized — API key invalid or insufficient permissions', action: 'report' },
|
|
27
|
+
[-1006]: { message: 'Unexpected response from exchange', action: 'wait_and_retry' },
|
|
28
|
+
[-1007]: { message: 'Exchange request timeout', action: 'check_and_retry' },
|
|
29
|
+
[-1014]: { message: 'Unsupported order combination', action: 'report' },
|
|
30
|
+
[-1016]: { message: 'Service shutting down', action: 'report' },
|
|
31
|
+
[-1020]: { message: 'Unsupported operation', action: 'report' },
|
|
32
|
+
};
|
|
33
|
+
/** Parse a CCXT/Binance error into a structured response. */
|
|
34
|
+
export function parseExchangeError(err) {
|
|
35
|
+
const error = err;
|
|
36
|
+
// Extract error code (CCXT wraps Binance errors)
|
|
37
|
+
let code = null;
|
|
38
|
+
let rawMessage = '';
|
|
39
|
+
if (typeof error === 'object' && error !== null) {
|
|
40
|
+
// CCXT errors often have a message like "binance {...}" or structured fields
|
|
41
|
+
rawMessage = String(error.message ?? error);
|
|
42
|
+
// Try to extract Binance error code from message
|
|
43
|
+
const codeMatch = rawMessage.match(/"code"\s*:\s*(-?\d+)/);
|
|
44
|
+
if (codeMatch) {
|
|
45
|
+
code = parseInt(codeMatch[1], 10);
|
|
46
|
+
}
|
|
47
|
+
// Also check if CCXT provides it directly
|
|
48
|
+
if (typeof error.code === 'number') {
|
|
49
|
+
code = error.code;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
rawMessage = String(err);
|
|
54
|
+
}
|
|
55
|
+
// Check for network errors
|
|
56
|
+
const isNetworkError = isNetworkRelated(rawMessage);
|
|
57
|
+
if (isNetworkError) {
|
|
58
|
+
return {
|
|
59
|
+
code: null,
|
|
60
|
+
message: rawMessage,
|
|
61
|
+
action: 'check_and_retry',
|
|
62
|
+
retryAfterMs: 1000,
|
|
63
|
+
isRetryable: true,
|
|
64
|
+
isNetworkError: true,
|
|
65
|
+
humanMessage: 'Network error communicating with exchange. Checking if order went through...',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Look up known error code
|
|
69
|
+
if (code !== null && BINANCE_ERRORS[code]) {
|
|
70
|
+
const known = BINANCE_ERRORS[code];
|
|
71
|
+
const retryAfterMs = known.action === 'wait_and_retry' ? (code === -1015 || code === -1003 ? 60_000 : 5_000) : 0;
|
|
72
|
+
return {
|
|
73
|
+
code,
|
|
74
|
+
message: rawMessage,
|
|
75
|
+
action: known.action,
|
|
76
|
+
retryAfterMs,
|
|
77
|
+
isRetryable: known.action !== 'report',
|
|
78
|
+
isNetworkError: false,
|
|
79
|
+
humanMessage: buildHumanMessage(code, known.message, rawMessage),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// Unknown error
|
|
83
|
+
logger.warn(TAG, `Unknown exchange error (code=${code}): ${rawMessage}`);
|
|
84
|
+
return {
|
|
85
|
+
code,
|
|
86
|
+
message: rawMessage,
|
|
87
|
+
action: 'report',
|
|
88
|
+
retryAfterMs: 0,
|
|
89
|
+
isRetryable: false,
|
|
90
|
+
isNetworkError: false,
|
|
91
|
+
humanMessage: `Exchange error: ${rawMessage}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Check if an error is network-related (timeout, connection reset, etc.). */
|
|
95
|
+
function isNetworkRelated(message) {
|
|
96
|
+
const patterns = [
|
|
97
|
+
'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND',
|
|
98
|
+
'network', 'timeout', 'socket hang up', 'EPIPE',
|
|
99
|
+
'EAI_AGAIN', 'EHOSTUNREACH', 'fetch failed',
|
|
100
|
+
];
|
|
101
|
+
const lower = message.toLowerCase();
|
|
102
|
+
return patterns.some(p => lower.includes(p.toLowerCase()));
|
|
103
|
+
}
|
|
104
|
+
/** Build a human-friendly error message. */
|
|
105
|
+
function buildHumanMessage(code, knownMessage, rawMessage) {
|
|
106
|
+
switch (code) {
|
|
107
|
+
case -2019: {
|
|
108
|
+
// Try to extract balance info from raw message
|
|
109
|
+
const match = rawMessage.match(/(\d+\.?\d*)\s*USDT/);
|
|
110
|
+
if (match)
|
|
111
|
+
return `Insufficient margin: available ${match[1]} USDT`;
|
|
112
|
+
return 'Insufficient margin — check your balance';
|
|
113
|
+
}
|
|
114
|
+
case -4131:
|
|
115
|
+
return `Order too small: below this symbol's minimum notional value. Check exchange info for the exact minimum.`;
|
|
116
|
+
case -1111:
|
|
117
|
+
return `Precision error: amount or price has too many decimal places. Order will be auto-corrected on retry.`;
|
|
118
|
+
case -1015:
|
|
119
|
+
case -1003:
|
|
120
|
+
return `Exchange rate limit hit. Wait 60 seconds before retrying.`;
|
|
121
|
+
case -2022:
|
|
122
|
+
return `Invalid stop price. For buy stops: stop must be above current price. For sell stops: stop must be below.`;
|
|
123
|
+
case -1021:
|
|
124
|
+
return `Clock out of sync with exchange. Syncing time automatically...`;
|
|
125
|
+
case -4028:
|
|
126
|
+
return `Cannot reduce position — no open position found. The position may have already been closed.`;
|
|
127
|
+
default:
|
|
128
|
+
return `Exchange error ${code}: ${knownMessage}`;
|
|
129
|
+
}
|
|
130
|
+
}
|