@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,318 @@
|
|
|
1
|
+
export interface PositionDecisionsClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
ingestToken: string;
|
|
4
|
+
fetchImpl?: typeof fetch;
|
|
5
|
+
requestTimeoutMs?: number;
|
|
6
|
+
maxAttempts?: number;
|
|
7
|
+
baseBackoffMs?: number;
|
|
8
|
+
backoffCapMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface PositionUpsertPayload {
|
|
11
|
+
symbol: string;
|
|
12
|
+
positionOpenAt: number;
|
|
13
|
+
side: 'long' | 'short';
|
|
14
|
+
currentSize: number;
|
|
15
|
+
avgEntryPrice: number;
|
|
16
|
+
/** Trading mode the position was opened in ('paper' | 'live'). Tags the
|
|
17
|
+
* journal row so paper and live positions can be segregated. Omitted only
|
|
18
|
+
* if the active adapter can't be resolved (treated as legacy/NULL). */
|
|
19
|
+
mode?: 'paper' | 'live';
|
|
20
|
+
}
|
|
21
|
+
export interface PositionEntryPayload {
|
|
22
|
+
positionId: string;
|
|
23
|
+
entryAt: number;
|
|
24
|
+
isScaleIn?: boolean;
|
|
25
|
+
thesis: string;
|
|
26
|
+
setupType: string;
|
|
27
|
+
regime: string;
|
|
28
|
+
regimeConfidence: number;
|
|
29
|
+
scorecardVerdict: string;
|
|
30
|
+
confluenceScore: number;
|
|
31
|
+
confluenceComponents?: Record<string, unknown>;
|
|
32
|
+
fillPrice: number;
|
|
33
|
+
fillSize: number;
|
|
34
|
+
exchangeTradeId?: string;
|
|
35
|
+
metadata?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
export interface PositionReviewPayload {
|
|
38
|
+
positionId: string;
|
|
39
|
+
reviewAt?: number;
|
|
40
|
+
verdict: 'hold' | 'add_on' | 'close_recommended';
|
|
41
|
+
thesisStatus: 'intact' | 'weakening' | 'invalidated' | 'evolving';
|
|
42
|
+
entryScore: number;
|
|
43
|
+
currentScore: number;
|
|
44
|
+
scoreDelta: number;
|
|
45
|
+
confluenceComponents: Record<string, unknown>;
|
|
46
|
+
regimeEntry: string;
|
|
47
|
+
regimeCurrent: string;
|
|
48
|
+
regimeConfidence: number;
|
|
49
|
+
rMultiple: number;
|
|
50
|
+
distanceToStopBps: number;
|
|
51
|
+
distanceToTargetBps?: number | null;
|
|
52
|
+
timeInPositionMinutes: number;
|
|
53
|
+
mfeR: number;
|
|
54
|
+
giveBackPct: number;
|
|
55
|
+
whatChanged: string;
|
|
56
|
+
invalidationTrigger: string;
|
|
57
|
+
restingBandImbalance05?: number | null;
|
|
58
|
+
cascadePulseAtReview?: 'quiet' | 'elevated' | 'active_cascade' | null;
|
|
59
|
+
coveragePctAtReview?: number | null;
|
|
60
|
+
imbalanceDeltaSinceEntry?: number | null;
|
|
61
|
+
metadata?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
export interface PositionClosePayload {
|
|
64
|
+
positionId: string;
|
|
65
|
+
closeAt: number;
|
|
66
|
+
closeReason: string;
|
|
67
|
+
closeAssessment: Record<string, unknown>;
|
|
68
|
+
scorecardVerdict: string;
|
|
69
|
+
confluenceScore: number;
|
|
70
|
+
regime: string;
|
|
71
|
+
regimeConfidence: number;
|
|
72
|
+
fillPrice: number;
|
|
73
|
+
fillSize: number;
|
|
74
|
+
realizedPnl: number;
|
|
75
|
+
realizedR: number;
|
|
76
|
+
mfeRAtClose: number;
|
|
77
|
+
giveBackPctAtClose: number;
|
|
78
|
+
exchangeTradeId?: string;
|
|
79
|
+
metadata?: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
/** Row shape from GET /api/internal/positions?status=open (DB-vs-exchange
|
|
82
|
+
* reconcile). Keyed off the DB row id so a wiped state-store can't hide it. */
|
|
83
|
+
export interface OpenPositionRow {
|
|
84
|
+
id: string;
|
|
85
|
+
symbol: string;
|
|
86
|
+
side: 'long' | 'short';
|
|
87
|
+
remainingSize: number;
|
|
88
|
+
entryPrice: number;
|
|
89
|
+
openedAt: number;
|
|
90
|
+
}
|
|
91
|
+
export interface OpenPositionsResponse {
|
|
92
|
+
positions: OpenPositionRow[];
|
|
93
|
+
}
|
|
94
|
+
export interface PositionDecisionsClientHealth {
|
|
95
|
+
inFlight: number;
|
|
96
|
+
totalPosted: number;
|
|
97
|
+
totalSucceeded: number;
|
|
98
|
+
totalDroppedTerminal: number;
|
|
99
|
+
totalDroppedRetriesExhausted: number;
|
|
100
|
+
}
|
|
101
|
+
export interface PriorReview {
|
|
102
|
+
reviewAt: number;
|
|
103
|
+
verdict: 'hold' | 'add_on' | 'close_recommended';
|
|
104
|
+
thesisStatus: 'intact' | 'weakening' | 'invalidated' | 'evolving';
|
|
105
|
+
scoreDelta: number;
|
|
106
|
+
currentScore: number;
|
|
107
|
+
rMultiple: number;
|
|
108
|
+
regimeCurrent: string;
|
|
109
|
+
whatChanged: string;
|
|
110
|
+
}
|
|
111
|
+
export interface VerdictStreak {
|
|
112
|
+
verdict: 'hold' | 'add_on' | 'close_recommended';
|
|
113
|
+
consecutiveCount: number;
|
|
114
|
+
thesisProgression: ('intact' | 'weakening' | 'invalidated' | 'evolving')[];
|
|
115
|
+
truncated: boolean;
|
|
116
|
+
}
|
|
117
|
+
export interface ReentryContext {
|
|
118
|
+
recentOpenedCount: number;
|
|
119
|
+
recentClosedCount: number;
|
|
120
|
+
recentClosedRealizedR: number[];
|
|
121
|
+
minutesSinceLastClose: number | null;
|
|
122
|
+
}
|
|
123
|
+
export interface PositionReviewContext {
|
|
124
|
+
positionId: string;
|
|
125
|
+
symbol: string;
|
|
126
|
+
side: 'long' | 'short';
|
|
127
|
+
openedAt: number;
|
|
128
|
+
reviews: PriorReview[];
|
|
129
|
+
verdictStreak: VerdictStreak | null;
|
|
130
|
+
reentryContext: ReentryContext | null;
|
|
131
|
+
}
|
|
132
|
+
export interface RecentReviewsResponse {
|
|
133
|
+
positions: PositionReviewContext[];
|
|
134
|
+
}
|
|
135
|
+
export interface ReviewOutcomesFilters {
|
|
136
|
+
verdict?: 'hold' | 'add_on' | 'close_recommended';
|
|
137
|
+
thesisStatus?: 'intact' | 'weakening' | 'invalidated' | 'evolving';
|
|
138
|
+
regimeCurrent?: string;
|
|
139
|
+
symbol?: string;
|
|
140
|
+
side?: 'long' | 'short';
|
|
141
|
+
setupType?: string;
|
|
142
|
+
daysBack?: number;
|
|
143
|
+
minScoreDelta?: number;
|
|
144
|
+
maxScoreDelta?: number;
|
|
145
|
+
closedOnly?: boolean;
|
|
146
|
+
sampleLimit?: number;
|
|
147
|
+
}
|
|
148
|
+
export interface ReviewOutcomesStats {
|
|
149
|
+
nTotal: number;
|
|
150
|
+
nClosed: number;
|
|
151
|
+
winRate: number | null;
|
|
152
|
+
medianR: number | null;
|
|
153
|
+
meanR: number | null;
|
|
154
|
+
medianGiveBack: number | null;
|
|
155
|
+
p25R: number | null;
|
|
156
|
+
p75R: number | null;
|
|
157
|
+
outcomeCounts: Record<string, number>;
|
|
158
|
+
}
|
|
159
|
+
export interface ReviewOutcomeSample {
|
|
160
|
+
reviewId: string;
|
|
161
|
+
positionId: string;
|
|
162
|
+
reviewAt: string;
|
|
163
|
+
symbol: string;
|
|
164
|
+
side: 'long' | 'short';
|
|
165
|
+
verdict: string;
|
|
166
|
+
thesisStatus: string;
|
|
167
|
+
regimeCurrent: string;
|
|
168
|
+
setupType: string | null;
|
|
169
|
+
scoreDelta: number;
|
|
170
|
+
rAtReview: number;
|
|
171
|
+
mfeAtReview: number;
|
|
172
|
+
whatChanged: string;
|
|
173
|
+
positionStatus: 'open' | 'closed';
|
|
174
|
+
closeAt: string | null;
|
|
175
|
+
finalRealizedR: number | null;
|
|
176
|
+
finalCloseReason: string | null;
|
|
177
|
+
minutesReviewToClose: number | null;
|
|
178
|
+
}
|
|
179
|
+
export interface ReviewOutcomesResponse {
|
|
180
|
+
filters: Record<string, unknown>;
|
|
181
|
+
stats: ReviewOutcomesStats;
|
|
182
|
+
sample: ReviewOutcomeSample[];
|
|
183
|
+
}
|
|
184
|
+
export interface RelevantLearning {
|
|
185
|
+
id: string;
|
|
186
|
+
title: string;
|
|
187
|
+
directive: string;
|
|
188
|
+
triggerCondition: Record<string, unknown> | null;
|
|
189
|
+
evidence: Record<string, unknown> | null;
|
|
190
|
+
lastValidatedAt: string | null;
|
|
191
|
+
updatedAt: string;
|
|
192
|
+
}
|
|
193
|
+
export interface RelevantLearningsResponse {
|
|
194
|
+
learnings: RelevantLearning[];
|
|
195
|
+
appliesAt: 'entry' | 'heartbeat' | 'close';
|
|
196
|
+
totalCandidates: number;
|
|
197
|
+
returnedCount: number;
|
|
198
|
+
}
|
|
199
|
+
export interface RelevantLearningsFilters {
|
|
200
|
+
appliesAt: 'entry' | 'heartbeat' | 'close';
|
|
201
|
+
setupType?: string;
|
|
202
|
+
regime?: string;
|
|
203
|
+
verdict?: 'hold' | 'add_on' | 'close_recommended';
|
|
204
|
+
}
|
|
205
|
+
export interface MyMinedPattern {
|
|
206
|
+
id: string;
|
|
207
|
+
patternType: string;
|
|
208
|
+
matchConditions: Record<string, unknown>;
|
|
209
|
+
sampleSize: number;
|
|
210
|
+
baselineValue: number | null;
|
|
211
|
+
observedValue: number | null;
|
|
212
|
+
effectSize: number;
|
|
213
|
+
pValue: number;
|
|
214
|
+
humanSummary: string;
|
|
215
|
+
minedAt: string;
|
|
216
|
+
dateRangeEnd: string | null;
|
|
217
|
+
promotedToLearningId: string | null;
|
|
218
|
+
}
|
|
219
|
+
export interface MyMinedPatternsResponse {
|
|
220
|
+
patterns: MyMinedPattern[];
|
|
221
|
+
totalReturned: number;
|
|
222
|
+
}
|
|
223
|
+
export interface MyMinedPatternsFilters {
|
|
224
|
+
withinDays?: number;
|
|
225
|
+
minSampleSize?: number;
|
|
226
|
+
maxPValue?: number;
|
|
227
|
+
includePromoted?: boolean;
|
|
228
|
+
}
|
|
229
|
+
export interface MyProposedLearning {
|
|
230
|
+
id: string;
|
|
231
|
+
confidence: 'hypothesis' | 'confirmed' | 'retired';
|
|
232
|
+
appliesAt: 'entry' | 'heartbeat' | 'close';
|
|
233
|
+
title: string;
|
|
234
|
+
directive: string;
|
|
235
|
+
triggerCondition: Record<string, unknown> | null;
|
|
236
|
+
evidence: Record<string, unknown> | null;
|
|
237
|
+
sourceMinedPatternId: string | null;
|
|
238
|
+
proposedBy: 'operator' | 'agent';
|
|
239
|
+
autoConfirmed: boolean;
|
|
240
|
+
createdAt: string;
|
|
241
|
+
updatedAt: string;
|
|
242
|
+
}
|
|
243
|
+
export interface MyProposedLearningsResponse {
|
|
244
|
+
learnings: MyProposedLearning[];
|
|
245
|
+
totalReturned: number;
|
|
246
|
+
}
|
|
247
|
+
export interface MyProposedLearningsFilters {
|
|
248
|
+
includeHypothesis?: boolean;
|
|
249
|
+
includeConfirmed?: boolean;
|
|
250
|
+
includeRetired?: boolean;
|
|
251
|
+
proposedBy?: 'agent' | 'operator';
|
|
252
|
+
}
|
|
253
|
+
export interface ProposeLearningPayload {
|
|
254
|
+
appliesAt: 'entry' | 'heartbeat' | 'close';
|
|
255
|
+
title: string;
|
|
256
|
+
directive: string;
|
|
257
|
+
sourceMinedPatternId?: string;
|
|
258
|
+
triggerCondition?: Record<string, unknown>;
|
|
259
|
+
evidence?: Record<string, unknown>;
|
|
260
|
+
}
|
|
261
|
+
export interface ProposeLearningResponse {
|
|
262
|
+
id: string;
|
|
263
|
+
confidence: 'hypothesis' | 'confirmed';
|
|
264
|
+
autoConfirmed: boolean;
|
|
265
|
+
trustMode: 'manual' | 'auto';
|
|
266
|
+
proposalsUsed: number;
|
|
267
|
+
proposalsLimit: number;
|
|
268
|
+
}
|
|
269
|
+
export declare class PositionDecisionsClient {
|
|
270
|
+
private readonly opts;
|
|
271
|
+
private inFlight;
|
|
272
|
+
private totalPosted;
|
|
273
|
+
private totalSucceeded;
|
|
274
|
+
private totalDroppedTerminal;
|
|
275
|
+
private totalDroppedRetriesExhausted;
|
|
276
|
+
constructor(options: PositionDecisionsClientOptions);
|
|
277
|
+
/** Awaited — the auto-capture pipeline needs the returned positionId before
|
|
278
|
+
* posting children. Returns null on terminal failure (caller logs + drops). */
|
|
279
|
+
postPosition(userId: string, payload: PositionUpsertPayload): Promise<string | null>;
|
|
280
|
+
/** Fire-and-forget. */
|
|
281
|
+
postEntry(userId: string, payload: PositionEntryPayload): void;
|
|
282
|
+
/** Fire-and-forget. */
|
|
283
|
+
postReview(userId: string, payload: PositionReviewPayload): void;
|
|
284
|
+
/** Fire-and-forget. */
|
|
285
|
+
postClose(userId: string, payload: PositionClosePayload): void;
|
|
286
|
+
/** DB-vs-exchange reconcile read: this tenant's open positions. Awaited.
|
|
287
|
+
* Returns null on terminal/retry-exhausted failure — the caller MUST treat
|
|
288
|
+
* null as UNKNOWN (never as "no open rows"), else a failed fetch would
|
|
289
|
+
* false-close the whole book. */
|
|
290
|
+
getOpenPositions(userId: string): Promise<OpenPositionsResponse | null>;
|
|
291
|
+
/** Read endpoint for the Phase 1 self-reflection feature. Awaited.
|
|
292
|
+
* Returns null on terminal/retry-exhausted failure (caller logs + degrades). */
|
|
293
|
+
getRecentReviews(userId: string, positionIds: string[], reviewLimit?: number): Promise<RecentReviewsResponse | null>;
|
|
294
|
+
/** Phase 2 outcome-attribution read. Aggregate stats + sample over the
|
|
295
|
+
* position_review_outcomes view filtered by the provided criteria. */
|
|
296
|
+
queryReviewOutcomes(userId: string, filters: ReviewOutcomesFilters): Promise<ReviewOutcomesResponse | null>;
|
|
297
|
+
/** Phase 4 — curated learnings read. The agent calls this at decision
|
|
298
|
+
* time (entry/heartbeat/close) to fetch confirmed learnings whose
|
|
299
|
+
* trigger_condition matches the current decision context. Returns null
|
|
300
|
+
* on terminal/retry-exhausted failure (caller logs + degrades to empty). */
|
|
301
|
+
getRelevantLearnings(userId: string, filters: RelevantLearningsFilters): Promise<RelevantLearningsResponse | null>;
|
|
302
|
+
/** Phase 4.5 — agent self-assessment: read this user's own unpromoted
|
|
303
|
+
* mined_patterns so the agent can pick candidates to propose. */
|
|
304
|
+
getMyMinedPatterns(userId: string, filters: MyMinedPatternsFilters): Promise<MyMinedPatternsResponse | null>;
|
|
305
|
+
/** Phase 4.5 — read this user's own learnings across confidence states, so
|
|
306
|
+
* the agent can dedup before proposing a fresh hypothesis. */
|
|
307
|
+
getMyProposedLearnings(userId: string, filters: MyProposedLearningsFilters): Promise<MyProposedLearningsResponse | null>;
|
|
308
|
+
/** Phase 4.5 — POST a new hypothesis (or auto-confirmed) learning. Confidence
|
|
309
|
+
* is server-determined by user trust mode; we cannot set it from here. */
|
|
310
|
+
proposeLearning(userId: string, payload: ProposeLearningPayload): Promise<ProposeLearningResponse | null>;
|
|
311
|
+
drain(): Promise<void>;
|
|
312
|
+
getHealth(): PositionDecisionsClientHealth;
|
|
313
|
+
private fireAndForget;
|
|
314
|
+
private run;
|
|
315
|
+
private runReturning;
|
|
316
|
+
private runGetReturning;
|
|
317
|
+
private sleepBackoff;
|
|
318
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// HTTP client for the webapp's /api/internal/position-{positions,entries,reviews,closes}
|
|
2
|
+
// ingest endpoints.
|
|
3
|
+
//
|
|
4
|
+
// Mirrors trade-store-client.ts retry semantics:
|
|
5
|
+
// - 2xx : success.
|
|
6
|
+
// - 4xx : terminal — drop + log loudly (caller bug or duplicate).
|
|
7
|
+
// - 5xx / network : retry with exponential backoff up to maxAttempts.
|
|
8
|
+
//
|
|
9
|
+
// Two callable surfaces:
|
|
10
|
+
// - postPosition() is awaited (returns the parent UUID); the auto-capture
|
|
11
|
+
// pipeline needs the id before posting children.
|
|
12
|
+
// - postEntry/Review/Close are fire-and-forget (don't block the WS hot path).
|
|
13
|
+
//
|
|
14
|
+
// All four routes accept the same auth: Bearer + X-User-Id headers.
|
|
15
|
+
import { logger, formatError } from '../logger.js';
|
|
16
|
+
const TAG = 'position-decisions-client';
|
|
17
|
+
export class PositionDecisionsClient {
|
|
18
|
+
opts;
|
|
19
|
+
inFlight = new Set();
|
|
20
|
+
totalPosted = 0;
|
|
21
|
+
totalSucceeded = 0;
|
|
22
|
+
totalDroppedTerminal = 0;
|
|
23
|
+
totalDroppedRetriesExhausted = 0;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.opts = {
|
|
26
|
+
baseUrl: options.baseUrl.replace(/\/+$/, ''),
|
|
27
|
+
ingestToken: options.ingestToken,
|
|
28
|
+
fetchImpl: options.fetchImpl ?? fetch,
|
|
29
|
+
requestTimeoutMs: options.requestTimeoutMs ?? 10_000,
|
|
30
|
+
maxAttempts: options.maxAttempts ?? 4,
|
|
31
|
+
baseBackoffMs: options.baseBackoffMs ?? 250,
|
|
32
|
+
backoffCapMs: options.backoffCapMs ?? 5_000,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Awaited — the auto-capture pipeline needs the returned positionId before
|
|
36
|
+
* posting children. Returns null on terminal failure (caller logs + drops). */
|
|
37
|
+
async postPosition(userId, payload) {
|
|
38
|
+
const result = await this.runReturning(userId, '/api/internal/positions', payload);
|
|
39
|
+
return result?.positionId ?? null;
|
|
40
|
+
}
|
|
41
|
+
/** Fire-and-forget. */
|
|
42
|
+
postEntry(userId, payload) {
|
|
43
|
+
this.fireAndForget(userId, '/api/internal/position-entries', payload);
|
|
44
|
+
}
|
|
45
|
+
/** Fire-and-forget. */
|
|
46
|
+
postReview(userId, payload) {
|
|
47
|
+
this.fireAndForget(userId, '/api/internal/position-reviews', payload);
|
|
48
|
+
}
|
|
49
|
+
/** Fire-and-forget. */
|
|
50
|
+
postClose(userId, payload) {
|
|
51
|
+
this.fireAndForget(userId, '/api/internal/position-closes', payload);
|
|
52
|
+
}
|
|
53
|
+
/** DB-vs-exchange reconcile read: this tenant's open positions. Awaited.
|
|
54
|
+
* Returns null on terminal/retry-exhausted failure — the caller MUST treat
|
|
55
|
+
* null as UNKNOWN (never as "no open rows"), else a failed fetch would
|
|
56
|
+
* false-close the whole book. */
|
|
57
|
+
async getOpenPositions(userId) {
|
|
58
|
+
return this.runGetReturning(userId, '/api/internal/positions?status=open');
|
|
59
|
+
}
|
|
60
|
+
/** Read endpoint for the Phase 1 self-reflection feature. Awaited.
|
|
61
|
+
* Returns null on terminal/retry-exhausted failure (caller logs + degrades). */
|
|
62
|
+
async getRecentReviews(userId, positionIds, reviewLimit) {
|
|
63
|
+
if (positionIds.length === 0)
|
|
64
|
+
return { positions: [] };
|
|
65
|
+
const qs = new URLSearchParams();
|
|
66
|
+
qs.set('positionIds', positionIds.join(','));
|
|
67
|
+
if (reviewLimit && reviewLimit > 0)
|
|
68
|
+
qs.set('reviewLimit', String(reviewLimit));
|
|
69
|
+
const path = `/api/internal/position-reviews/recent?${qs.toString()}`;
|
|
70
|
+
return this.runGetReturning(userId, path);
|
|
71
|
+
}
|
|
72
|
+
/** Phase 2 outcome-attribution read. Aggregate stats + sample over the
|
|
73
|
+
* position_review_outcomes view filtered by the provided criteria. */
|
|
74
|
+
async queryReviewOutcomes(userId, filters) {
|
|
75
|
+
const qs = new URLSearchParams();
|
|
76
|
+
if (filters.verdict)
|
|
77
|
+
qs.set('verdict', filters.verdict);
|
|
78
|
+
if (filters.thesisStatus)
|
|
79
|
+
qs.set('thesis_status', filters.thesisStatus);
|
|
80
|
+
if (filters.regimeCurrent)
|
|
81
|
+
qs.set('regime_current', filters.regimeCurrent);
|
|
82
|
+
if (filters.symbol)
|
|
83
|
+
qs.set('symbol', filters.symbol);
|
|
84
|
+
if (filters.side)
|
|
85
|
+
qs.set('side', filters.side);
|
|
86
|
+
if (filters.setupType)
|
|
87
|
+
qs.set('setup_type', filters.setupType);
|
|
88
|
+
if (filters.daysBack !== undefined)
|
|
89
|
+
qs.set('days_back', String(filters.daysBack));
|
|
90
|
+
if (filters.minScoreDelta !== undefined) {
|
|
91
|
+
qs.set('min_score_delta', String(filters.minScoreDelta));
|
|
92
|
+
}
|
|
93
|
+
if (filters.maxScoreDelta !== undefined) {
|
|
94
|
+
qs.set('max_score_delta', String(filters.maxScoreDelta));
|
|
95
|
+
}
|
|
96
|
+
if (filters.closedOnly)
|
|
97
|
+
qs.set('closed_only', '1');
|
|
98
|
+
if (filters.sampleLimit !== undefined)
|
|
99
|
+
qs.set('sample_limit', String(filters.sampleLimit));
|
|
100
|
+
const path = `/api/internal/positions/reviews/with-outcomes${qs.toString() ? `?${qs.toString()}` : ''}`;
|
|
101
|
+
return this.runGetReturning(userId, path);
|
|
102
|
+
}
|
|
103
|
+
/** Phase 4 — curated learnings read. The agent calls this at decision
|
|
104
|
+
* time (entry/heartbeat/close) to fetch confirmed learnings whose
|
|
105
|
+
* trigger_condition matches the current decision context. Returns null
|
|
106
|
+
* on terminal/retry-exhausted failure (caller logs + degrades to empty). */
|
|
107
|
+
async getRelevantLearnings(userId, filters) {
|
|
108
|
+
const qs = new URLSearchParams();
|
|
109
|
+
qs.set('applies_at', filters.appliesAt);
|
|
110
|
+
if (filters.setupType)
|
|
111
|
+
qs.set('setup_type', filters.setupType);
|
|
112
|
+
if (filters.regime)
|
|
113
|
+
qs.set('regime', filters.regime);
|
|
114
|
+
if (filters.verdict)
|
|
115
|
+
qs.set('verdict', filters.verdict);
|
|
116
|
+
const path = `/api/internal/learnings?${qs.toString()}`;
|
|
117
|
+
return this.runGetReturning(userId, path);
|
|
118
|
+
}
|
|
119
|
+
/** Phase 4.5 — agent self-assessment: read this user's own unpromoted
|
|
120
|
+
* mined_patterns so the agent can pick candidates to propose. */
|
|
121
|
+
async getMyMinedPatterns(userId, filters) {
|
|
122
|
+
const qs = new URLSearchParams();
|
|
123
|
+
if (filters.withinDays !== undefined)
|
|
124
|
+
qs.set('within_days', String(filters.withinDays));
|
|
125
|
+
if (filters.minSampleSize !== undefined)
|
|
126
|
+
qs.set('min_sample_size', String(filters.minSampleSize));
|
|
127
|
+
if (filters.maxPValue !== undefined)
|
|
128
|
+
qs.set('max_p_value', String(filters.maxPValue));
|
|
129
|
+
if (filters.includePromoted)
|
|
130
|
+
qs.set('include_promoted', '1');
|
|
131
|
+
const q = qs.toString();
|
|
132
|
+
const path = `/api/internal/learnings/my-patterns${q ? `?${q}` : ''}`;
|
|
133
|
+
return this.runGetReturning(userId, path);
|
|
134
|
+
}
|
|
135
|
+
/** Phase 4.5 — read this user's own learnings across confidence states, so
|
|
136
|
+
* the agent can dedup before proposing a fresh hypothesis. */
|
|
137
|
+
async getMyProposedLearnings(userId, filters) {
|
|
138
|
+
const qs = new URLSearchParams();
|
|
139
|
+
if (filters.includeHypothesis === false)
|
|
140
|
+
qs.set('include_hypothesis', '0');
|
|
141
|
+
if (filters.includeConfirmed === false)
|
|
142
|
+
qs.set('include_confirmed', '0');
|
|
143
|
+
if (filters.includeRetired)
|
|
144
|
+
qs.set('include_retired', '1');
|
|
145
|
+
if (filters.proposedBy)
|
|
146
|
+
qs.set('proposed_by', filters.proposedBy);
|
|
147
|
+
const q = qs.toString();
|
|
148
|
+
const path = `/api/internal/learnings/my-proposals${q ? `?${q}` : ''}`;
|
|
149
|
+
return this.runGetReturning(userId, path);
|
|
150
|
+
}
|
|
151
|
+
/** Phase 4.5 — POST a new hypothesis (or auto-confirmed) learning. Confidence
|
|
152
|
+
* is server-determined by user trust mode; we cannot set it from here. */
|
|
153
|
+
async proposeLearning(userId, payload) {
|
|
154
|
+
return this.runReturning(userId, '/api/internal/learnings/propose', payload);
|
|
155
|
+
}
|
|
156
|
+
async drain() {
|
|
157
|
+
if (this.inFlight.size === 0)
|
|
158
|
+
return;
|
|
159
|
+
logger.info(TAG, `Draining ${this.inFlight.size} in-flight decision post(s)...`);
|
|
160
|
+
await Promise.allSettled(Array.from(this.inFlight));
|
|
161
|
+
}
|
|
162
|
+
getHealth() {
|
|
163
|
+
return {
|
|
164
|
+
inFlight: this.inFlight.size,
|
|
165
|
+
totalPosted: this.totalPosted,
|
|
166
|
+
totalSucceeded: this.totalSucceeded,
|
|
167
|
+
totalDroppedTerminal: this.totalDroppedTerminal,
|
|
168
|
+
totalDroppedRetriesExhausted: this.totalDroppedRetriesExhausted,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// ---- Internals ----
|
|
172
|
+
fireAndForget(userId, path, body) {
|
|
173
|
+
this.totalPosted++;
|
|
174
|
+
const promise = this.run(userId, path, body).finally(() => {
|
|
175
|
+
this.inFlight.delete(promise);
|
|
176
|
+
});
|
|
177
|
+
this.inFlight.add(promise);
|
|
178
|
+
}
|
|
179
|
+
async run(userId, path, body) {
|
|
180
|
+
await this.runReturning(userId, path, body);
|
|
181
|
+
}
|
|
182
|
+
async runReturning(userId, path, body) {
|
|
183
|
+
const url = `${this.opts.baseUrl}${path}`;
|
|
184
|
+
const json = JSON.stringify(body);
|
|
185
|
+
for (let attempt = 1; attempt <= this.opts.maxAttempts; attempt++) {
|
|
186
|
+
try {
|
|
187
|
+
const ac = new AbortController();
|
|
188
|
+
const tid = setTimeout(() => ac.abort(), this.opts.requestTimeoutMs);
|
|
189
|
+
let res;
|
|
190
|
+
try {
|
|
191
|
+
res = await this.opts.fetchImpl(url, {
|
|
192
|
+
method: 'POST',
|
|
193
|
+
headers: {
|
|
194
|
+
'content-type': 'application/json',
|
|
195
|
+
authorization: `Bearer ${this.opts.ingestToken}`,
|
|
196
|
+
'x-user-id': userId,
|
|
197
|
+
},
|
|
198
|
+
body: json,
|
|
199
|
+
signal: ac.signal,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
clearTimeout(tid);
|
|
204
|
+
}
|
|
205
|
+
if (res.status >= 200 && res.status < 300) {
|
|
206
|
+
this.totalSucceeded++;
|
|
207
|
+
try {
|
|
208
|
+
return (await res.json());
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (res.status >= 400 && res.status < 500) {
|
|
215
|
+
this.totalDroppedTerminal++;
|
|
216
|
+
let errBody = '';
|
|
217
|
+
try {
|
|
218
|
+
errBody = await res.text();
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// ignore
|
|
222
|
+
}
|
|
223
|
+
logger.warn(TAG, `POST ${url} → ${res.status} (terminal, dropped). userId=${userId} body=${errBody.slice(0, 200)}`);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
logger.warn(TAG, `POST ${url} → ${res.status} (attempt ${attempt}/${this.opts.maxAttempts}).`);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
logger.warn(TAG, `POST ${url} threw (attempt ${attempt}/${this.opts.maxAttempts}): ${formatError(err)}`);
|
|
230
|
+
}
|
|
231
|
+
if (attempt < this.opts.maxAttempts) {
|
|
232
|
+
await this.sleepBackoff(attempt);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this.totalDroppedRetriesExhausted++;
|
|
236
|
+
logger.error(TAG, `POST ${url} dropped after ${this.opts.maxAttempts} attempts. userId=${userId}.`);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
async runGetReturning(userId, path) {
|
|
240
|
+
const url = `${this.opts.baseUrl}${path}`;
|
|
241
|
+
for (let attempt = 1; attempt <= this.opts.maxAttempts; attempt++) {
|
|
242
|
+
try {
|
|
243
|
+
const ac = new AbortController();
|
|
244
|
+
const tid = setTimeout(() => ac.abort(), this.opts.requestTimeoutMs);
|
|
245
|
+
let res;
|
|
246
|
+
try {
|
|
247
|
+
res = await this.opts.fetchImpl(url, {
|
|
248
|
+
method: 'GET',
|
|
249
|
+
headers: {
|
|
250
|
+
authorization: `Bearer ${this.opts.ingestToken}`,
|
|
251
|
+
'x-user-id': userId,
|
|
252
|
+
},
|
|
253
|
+
signal: ac.signal,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
clearTimeout(tid);
|
|
258
|
+
}
|
|
259
|
+
if (res.status >= 200 && res.status < 300) {
|
|
260
|
+
try {
|
|
261
|
+
return (await res.json());
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (res.status >= 400 && res.status < 500) {
|
|
268
|
+
let errBody = '';
|
|
269
|
+
try {
|
|
270
|
+
errBody = await res.text();
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// ignore
|
|
274
|
+
}
|
|
275
|
+
logger.warn(TAG, `GET ${url} → ${res.status} (terminal, dropped). userId=${userId} body=${errBody.slice(0, 200)}`);
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
logger.warn(TAG, `GET ${url} → ${res.status} (attempt ${attempt}/${this.opts.maxAttempts}).`);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
logger.warn(TAG, `GET ${url} threw (attempt ${attempt}/${this.opts.maxAttempts}): ${formatError(err)}`);
|
|
282
|
+
}
|
|
283
|
+
if (attempt < this.opts.maxAttempts) {
|
|
284
|
+
await this.sleepBackoff(attempt);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
logger.error(TAG, `GET ${url} failed after ${this.opts.maxAttempts} attempts. userId=${userId}.`);
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
async sleepBackoff(attempt) {
|
|
291
|
+
const exp = Math.min(this.opts.baseBackoffMs * 2 ** (attempt - 1), this.opts.backoffCapMs);
|
|
292
|
+
const jitter = exp * 0.2 * (Math.random() * 2 - 1);
|
|
293
|
+
const delay = Math.max(0, Math.round(exp + jitter));
|
|
294
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PositionDecisionsClient } from './position-decisions-client.js';
|
|
2
|
+
export interface DbVsExchangeContext {
|
|
3
|
+
decisionsClient?: PositionDecisionsClient;
|
|
4
|
+
userId?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Close webapp `positions` rows that are status='open' but absent from the
|
|
8
|
+
* (trusted) exchange snapshot. Returns the number of synthetic closes posted.
|
|
9
|
+
*
|
|
10
|
+
* @param exchangeSymbols symbols from a TRUSTED snapshot (getPositionsOrNull()
|
|
11
|
+
* !== null). Pass only when the fetch genuinely succeeded.
|
|
12
|
+
*/
|
|
13
|
+
export declare function reconcileDbOpenVsExchange(ctx: DbVsExchangeContext, exchangeSymbols: Iterable<string>, nowMs?: number): Promise<number>;
|