@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,52 @@
|
|
|
1
|
+
import type { PositionMetadata } from '../simulator/types.js';
|
|
2
|
+
export interface PendingEntry {
|
|
3
|
+
/** Exchange-issued orderId — the lookup key when the WS fill arrives. */
|
|
4
|
+
orderId: string;
|
|
5
|
+
/** Canonical (un-suffixed) symbol. */
|
|
6
|
+
symbol: string;
|
|
7
|
+
/** Side the agent intended at create_order time. */
|
|
8
|
+
side: 'buy' | 'sell';
|
|
9
|
+
metadata: PositionMetadata;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
/** Epoch ms after which this entry is dropped. */
|
|
12
|
+
expiresAt: number;
|
|
13
|
+
}
|
|
14
|
+
export type NowFn = () => number;
|
|
15
|
+
export interface PendingEntryStoreOptions {
|
|
16
|
+
pluginId?: string;
|
|
17
|
+
basePath?: string;
|
|
18
|
+
now?: NowFn;
|
|
19
|
+
ttlMs?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare class PendingEntryStore {
|
|
22
|
+
private readonly filePath;
|
|
23
|
+
private readonly dir;
|
|
24
|
+
private readonly now;
|
|
25
|
+
private readonly ttlMs;
|
|
26
|
+
private entries;
|
|
27
|
+
constructor(opts?: PendingEntryStoreOptions);
|
|
28
|
+
/** Stash metadata until the WS fill for this orderId arrives. */
|
|
29
|
+
put(input: {
|
|
30
|
+
orderId: string;
|
|
31
|
+
symbol: string;
|
|
32
|
+
side: 'buy' | 'sell';
|
|
33
|
+
metadata: PositionMetadata;
|
|
34
|
+
}): void;
|
|
35
|
+
/** Retrieve metadata stashed for an orderId. Does NOT remove the entry —
|
|
36
|
+
* callers should call `consume(orderId)` to remove on first match, since
|
|
37
|
+
* multiple partial fills on the same limit order all share one orderId
|
|
38
|
+
* and the FIRST fill is the entry; subsequent are scale-ins of an already-
|
|
39
|
+
* registered position (state-store branch handles those). */
|
|
40
|
+
get(orderId: string): PendingEntry | undefined;
|
|
41
|
+
/** Atomic get + remove. Use this when you've already consumed the metadata
|
|
42
|
+
* to post an entry row and don't want it picked up again. */
|
|
43
|
+
consume(orderId: string): PendingEntry | undefined;
|
|
44
|
+
/** Remove an entry without retrieval (e.g. on cancel). */
|
|
45
|
+
drop(orderId: string): boolean;
|
|
46
|
+
size(): number;
|
|
47
|
+
getFilePath(): string;
|
|
48
|
+
private pruneExpired;
|
|
49
|
+
private loadOrInit;
|
|
50
|
+
private persist;
|
|
51
|
+
private validateFile;
|
|
52
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// Pending-entry metadata cache.
|
|
2
|
+
//
|
|
3
|
+
// When the agent calls create_order with v2.10.0 metadata (thesis, setup_type,
|
|
4
|
+
// regime, scorecard_verdict, confluence_score, etc.) for a LIMIT order that
|
|
5
|
+
// doesn't fill immediately, the metadata is stashed here keyed by the
|
|
6
|
+
// exchange's orderId. When the WS-driven fill arrives later (could be seconds
|
|
7
|
+
// or hours), `onWsFillObserved` looks up the metadata and posts the entry /
|
|
8
|
+
// scale-in decision row with the original rationale intact.
|
|
9
|
+
//
|
|
10
|
+
// Without this cache, limit-order entries land in `position_entries` with
|
|
11
|
+
// thesis='(no thesis recorded)' / setupType='unknown' — useless for Phase 2
|
|
12
|
+
// outcome attribution and Phase 3 mining.
|
|
13
|
+
//
|
|
14
|
+
// Persistence: same atomic-rename pattern as bracket-ledger / position-state-
|
|
15
|
+
// store. File at ~/.openclaw/plugins/<pluginId>/pending-entry-metadata.json.
|
|
16
|
+
//
|
|
17
|
+
// TTL: entries auto-expire 24 hours after creation (limit orders rarely live
|
|
18
|
+
// longer; expired entries are pruned on every load + every write to keep the
|
|
19
|
+
// file bounded).
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { resolvePluginsBaseDir } from '../util/plugin-paths.js';
|
|
23
|
+
import { logger, formatError } from '../logger.js';
|
|
24
|
+
const TAG = 'pending-entry-metadata';
|
|
25
|
+
const DEFAULT_PLUGIN_ID = 'reefclaw-paper-trading';
|
|
26
|
+
const STATE_FILENAME = 'pending-entry-metadata.json';
|
|
27
|
+
const SCHEMA_VERSION = 1;
|
|
28
|
+
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
29
|
+
const defaultNow = () => Date.now();
|
|
30
|
+
export class PendingEntryStore {
|
|
31
|
+
filePath;
|
|
32
|
+
dir;
|
|
33
|
+
now;
|
|
34
|
+
ttlMs;
|
|
35
|
+
entries = new Map();
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
const base = resolvePluginsBaseDir(opts?.basePath);
|
|
38
|
+
this.dir = join(base, opts?.pluginId ?? DEFAULT_PLUGIN_ID);
|
|
39
|
+
this.filePath = join(this.dir, STATE_FILENAME);
|
|
40
|
+
this.now = opts?.now ?? defaultNow;
|
|
41
|
+
this.ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
42
|
+
if (!existsSync(this.dir))
|
|
43
|
+
mkdirSync(this.dir, { recursive: true });
|
|
44
|
+
this.loadOrInit();
|
|
45
|
+
}
|
|
46
|
+
/** Stash metadata until the WS fill for this orderId arrives. */
|
|
47
|
+
put(input) {
|
|
48
|
+
const ts = this.now();
|
|
49
|
+
this.entries.set(input.orderId, {
|
|
50
|
+
orderId: input.orderId,
|
|
51
|
+
symbol: input.symbol,
|
|
52
|
+
side: input.side,
|
|
53
|
+
metadata: input.metadata,
|
|
54
|
+
createdAt: ts,
|
|
55
|
+
expiresAt: ts + this.ttlMs,
|
|
56
|
+
});
|
|
57
|
+
this.persist();
|
|
58
|
+
}
|
|
59
|
+
/** Retrieve metadata stashed for an orderId. Does NOT remove the entry —
|
|
60
|
+
* callers should call `consume(orderId)` to remove on first match, since
|
|
61
|
+
* multiple partial fills on the same limit order all share one orderId
|
|
62
|
+
* and the FIRST fill is the entry; subsequent are scale-ins of an already-
|
|
63
|
+
* registered position (state-store branch handles those). */
|
|
64
|
+
get(orderId) {
|
|
65
|
+
this.pruneExpired();
|
|
66
|
+
return this.entries.get(orderId);
|
|
67
|
+
}
|
|
68
|
+
/** Atomic get + remove. Use this when you've already consumed the metadata
|
|
69
|
+
* to post an entry row and don't want it picked up again. */
|
|
70
|
+
consume(orderId) {
|
|
71
|
+
this.pruneExpired();
|
|
72
|
+
const entry = this.entries.get(orderId);
|
|
73
|
+
if (!entry)
|
|
74
|
+
return undefined;
|
|
75
|
+
this.entries.delete(orderId);
|
|
76
|
+
this.persist();
|
|
77
|
+
return entry;
|
|
78
|
+
}
|
|
79
|
+
/** Remove an entry without retrieval (e.g. on cancel). */
|
|
80
|
+
drop(orderId) {
|
|
81
|
+
const existed = this.entries.delete(orderId);
|
|
82
|
+
if (existed)
|
|
83
|
+
this.persist();
|
|
84
|
+
return existed;
|
|
85
|
+
}
|
|
86
|
+
size() {
|
|
87
|
+
return this.entries.size;
|
|
88
|
+
}
|
|
89
|
+
getFilePath() {
|
|
90
|
+
return this.filePath;
|
|
91
|
+
}
|
|
92
|
+
// ---- Internals ----
|
|
93
|
+
pruneExpired() {
|
|
94
|
+
const ts = this.now();
|
|
95
|
+
let pruned = false;
|
|
96
|
+
for (const [orderId, entry] of this.entries) {
|
|
97
|
+
if (entry.expiresAt <= ts) {
|
|
98
|
+
this.entries.delete(orderId);
|
|
99
|
+
pruned = true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (pruned)
|
|
103
|
+
this.persist();
|
|
104
|
+
}
|
|
105
|
+
loadOrInit() {
|
|
106
|
+
if (!existsSync(this.filePath)) {
|
|
107
|
+
logger.info(TAG, `No state file at ${this.filePath}; starting empty`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
let raw;
|
|
111
|
+
try {
|
|
112
|
+
raw = readFileSync(this.filePath, 'utf8');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
throw new Error(`pending-entry-metadata: failed to read ${this.filePath}: ${formatError(err)}`);
|
|
116
|
+
}
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
throw new Error(`pending-entry-metadata: ${this.filePath} is not valid JSON: ${formatError(err)}`);
|
|
123
|
+
}
|
|
124
|
+
const file = this.validateFile(parsed);
|
|
125
|
+
const ts = this.now();
|
|
126
|
+
let kept = 0;
|
|
127
|
+
let dropped = 0;
|
|
128
|
+
for (const entry of file.entries) {
|
|
129
|
+
if (entry.expiresAt > ts) {
|
|
130
|
+
this.entries.set(entry.orderId, entry);
|
|
131
|
+
kept++;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
dropped++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
logger.info(TAG, `Loaded ${kept} pending entries from ${this.filePath}` +
|
|
138
|
+
(dropped > 0 ? ` (pruned ${dropped} expired)` : ''));
|
|
139
|
+
if (dropped > 0)
|
|
140
|
+
this.persist();
|
|
141
|
+
}
|
|
142
|
+
persist() {
|
|
143
|
+
const file = {
|
|
144
|
+
schemaVersion: SCHEMA_VERSION,
|
|
145
|
+
entries: Array.from(this.entries.values()),
|
|
146
|
+
};
|
|
147
|
+
const tempPath = `${this.filePath}.tmp`;
|
|
148
|
+
writeFileSync(tempPath, JSON.stringify(file, null, 2));
|
|
149
|
+
renameSync(tempPath, this.filePath);
|
|
150
|
+
}
|
|
151
|
+
validateFile(raw) {
|
|
152
|
+
if (!isRecord(raw))
|
|
153
|
+
throw new Error('pending-entry-metadata: file root is not an object');
|
|
154
|
+
if (raw.schemaVersion !== SCHEMA_VERSION) {
|
|
155
|
+
throw new Error(`pending-entry-metadata: unsupported schemaVersion ${String(raw.schemaVersion)} (expected ${SCHEMA_VERSION})`);
|
|
156
|
+
}
|
|
157
|
+
if (!Array.isArray(raw.entries))
|
|
158
|
+
throw new Error('pending-entry-metadata: entries is not an array');
|
|
159
|
+
const entries = [];
|
|
160
|
+
for (const e of raw.entries) {
|
|
161
|
+
if (!isRecord(e))
|
|
162
|
+
throw new Error('pending-entry-metadata: entry is not an object');
|
|
163
|
+
if (typeof e.orderId !== 'string' || e.orderId.length === 0) {
|
|
164
|
+
throw new Error('pending-entry-metadata: entry missing orderId');
|
|
165
|
+
}
|
|
166
|
+
if (typeof e.symbol !== 'string' || e.symbol.length === 0) {
|
|
167
|
+
throw new Error('pending-entry-metadata: entry missing symbol');
|
|
168
|
+
}
|
|
169
|
+
if (e.side !== 'buy' && e.side !== 'sell') {
|
|
170
|
+
throw new Error(`pending-entry-metadata: bad side ${String(e.side)}`);
|
|
171
|
+
}
|
|
172
|
+
if (typeof e.expiresAt !== 'number' || !Number.isFinite(e.expiresAt)) {
|
|
173
|
+
throw new Error('pending-entry-metadata: missing/invalid expiresAt');
|
|
174
|
+
}
|
|
175
|
+
entries.push(e);
|
|
176
|
+
}
|
|
177
|
+
return { schemaVersion: SCHEMA_VERSION, entries };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function isRecord(v) {
|
|
181
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
182
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { PositionDecisionsClient } from './position-decisions-client.js';
|
|
2
|
+
import type { PositionStateStore } from '../live/position-state-store.js';
|
|
3
|
+
import type { CcxtOrder } from '../types.js';
|
|
4
|
+
import type { PositionMetadata } from '../simulator/types.js';
|
|
5
|
+
import type { PendingEntryStore } from './pending-entry-metadata.js';
|
|
6
|
+
export interface AutoCaptureContext {
|
|
7
|
+
decisionsClient?: PositionDecisionsClient;
|
|
8
|
+
stateStore?: PositionStateStore;
|
|
9
|
+
/** Stash for create_order metadata that arrives BEFORE the fill (limit
|
|
10
|
+
* orders that don't fill immediately). Looked up by exchangeOrderId when
|
|
11
|
+
* the WS fill arrives via `onWsFillObserved`. */
|
|
12
|
+
pendingEntries?: PendingEntryStore;
|
|
13
|
+
userId?: string;
|
|
14
|
+
/** When true, a reduce-only fill that takes a journaled position flat is
|
|
15
|
+
* auto-captured as an exact close (bracket SL/TP fill or other close that
|
|
16
|
+
* bypassed close_position). Default behaviour when unset is the legacy
|
|
17
|
+
* no-op (reduce-only fills ignored). Env kill-switch: RC_JOURNAL_CLOSE_ON_FILL
|
|
18
|
+
* (set 'off' to disable). See docs/CLAUDE/position-journal.md (close-bypass). */
|
|
19
|
+
closeOnReduceOnlyFill?: boolean;
|
|
20
|
+
/** Resolve the active trading book ('paper' | 'live') at capture time, read
|
|
21
|
+
* from the current adapter (so it follows a runtime reconnect). Tags the
|
|
22
|
+
* journal position row so paper and live entries can be segregated. */
|
|
23
|
+
resolveMode?: () => 'paper' | 'live';
|
|
24
|
+
}
|
|
25
|
+
export interface CreateOrderInputs {
|
|
26
|
+
symbol: string;
|
|
27
|
+
side: 'buy' | 'sell';
|
|
28
|
+
metadata?: PositionMetadata;
|
|
29
|
+
}
|
|
30
|
+
/** Called after a successful adapter.createOrder().
|
|
31
|
+
*
|
|
32
|
+
* For PR 1, we treat any non-zero `filled` quantity as an entry event and
|
|
33
|
+
* upsert the parent position + post the entry decision row. Limit orders that
|
|
34
|
+
* haven't filled yet are skipped (caller can call us again from the WS-ingest
|
|
35
|
+
* pipeline once they fill — that wiring is the follow-up).
|
|
36
|
+
*
|
|
37
|
+
* Always-fail-open: webapp errors are logged but never thrown.
|
|
38
|
+
*/
|
|
39
|
+
export declare function onCreateOrderFilled(ctx: AutoCaptureContext, inputs: CreateOrderInputs, order: CcxtOrder): Promise<void>;
|
|
40
|
+
export interface ClosePositionInputs {
|
|
41
|
+
symbol: string;
|
|
42
|
+
closeReason: string;
|
|
43
|
+
closeAssessment: Record<string, unknown>;
|
|
44
|
+
/** Optional v2.10.0 inputs the agent may have passed alongside reason+assessment. */
|
|
45
|
+
scorecardVerdict?: string;
|
|
46
|
+
confluenceScore?: number;
|
|
47
|
+
regime?: string;
|
|
48
|
+
regimeConfidence?: number;
|
|
49
|
+
}
|
|
50
|
+
/** Called after a successful adapter.closePosition() with the v2.10.0 reason +
|
|
51
|
+
* assessment from close_position.ts. Looks up the cached position UUID, posts
|
|
52
|
+
* the close decision row, and drops the local state entry. */
|
|
53
|
+
export declare function onClosePositionFilled(ctx: AutoCaptureContext, inputs: ClosePositionInputs, order: CcxtOrder): Promise<void>;
|
|
54
|
+
/** WS-driven fill observed. Resolves the PR1 deferral that limit-order fills
|
|
55
|
+
* + scale-ins were silently dropped. Branches on three cases:
|
|
56
|
+
*
|
|
57
|
+
* - reduceOnly fill → ignored here (close path runs from close_position
|
|
58
|
+
* tool with v2.10.0 reason+assessment; bracket SL/TP
|
|
59
|
+
* fills also reduce-only and the legacy bracket
|
|
60
|
+
* handler covers those).
|
|
61
|
+
* - new-symbol entry → no state-store row → register the position +
|
|
62
|
+
* post entry decision row (uses pending metadata
|
|
63
|
+
* from the cache, or '(no thesis recorded)' fallback).
|
|
64
|
+
* - same-side fill on → state-store row exists with same side → SCALE-IN.
|
|
65
|
+
* existing position Posts a position_entries row with is_scale_in=true.
|
|
66
|
+
* Does NOT create a new positions row (single parent
|
|
67
|
+
* per open exposure).
|
|
68
|
+
* - opposite-side fill → state-store has same symbol but opposite side.
|
|
69
|
+
* on existing position Treated as a manual flatten that bypassed the
|
|
70
|
+
* close_position tool — skip (cleaner to require
|
|
71
|
+
* close_position for proper rationale capture).
|
|
72
|
+
*
|
|
73
|
+
* Dedup: when create_order's market-order path already captured an entry
|
|
74
|
+
* synchronously, the state-store entry's `openedFromExchangeTradeId` matches
|
|
75
|
+
* this fill's orderId. We detect that and skip to avoid double-posting. */
|
|
76
|
+
export interface WsFillInputs {
|
|
77
|
+
symbol: string;
|
|
78
|
+
side: 'buy' | 'sell';
|
|
79
|
+
exchangeOrderId: string;
|
|
80
|
+
exchangeTradeId: string;
|
|
81
|
+
fillPrice: number;
|
|
82
|
+
fillSize: number;
|
|
83
|
+
reduceOnly: boolean;
|
|
84
|
+
/** Binance-exact realised PnL on this specific fill (`o.rp`). Used to sum the
|
|
85
|
+
* position's realised PnL when a reduce-only fill takes it flat. */
|
|
86
|
+
realizedPnl?: number;
|
|
87
|
+
/** Client order id of the fill. Lets the reduce-only close handler tag a
|
|
88
|
+
* bracket SL/TP fill (cid is a bracket cid) vs an external reduce-only close. */
|
|
89
|
+
clientOrderId?: string;
|
|
90
|
+
/** Timestamp from the exchange. Falls back to Date.now() if not provided. */
|
|
91
|
+
exchangeTimeMs?: number;
|
|
92
|
+
}
|
|
93
|
+
export declare function onWsFillObserved(ctx: AutoCaptureContext, fill: WsFillInputs): Promise<void>;
|
|
94
|
+
/** Pinned-plan subset persisted to position_entries.metadata (free-form JSONB).
|
|
95
|
+
* Keys are snake_case per the canonical-JSONB-key rule (CLAUDE.md learning-loop:
|
|
96
|
+
* a camelCase mismatch previously broke learning matching). Returns undefined
|
|
97
|
+
* when neither field is present so we never write an empty object. */
|
|
98
|
+
export declare function buildEntryPlanMetadata(md: PositionMetadata | undefined): Record<string, unknown> | undefined;
|