@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,6 @@
|
|
|
1
|
+
// Tool: get_sizing — fetches Half-Kelly position sizing recommendation from Intelligence API.
|
|
2
|
+
// Returns recommended size as % of account, Kelly fraction, win rate, avg W/L ratio, vol adjustment.
|
|
3
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
4
|
+
export async function getSizingTool(args, deps) {
|
|
5
|
+
return fetchIntelApi(`/api/sizing/${enc(args.symbol)}`, deps);
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Tool: get_trade_feedback — fetches structured performance feedback from Intelligence API.
|
|
2
|
+
// Returns per-setup-type win rates, regime breakdown, recent outcomes, and current streak.
|
|
3
|
+
// Designed for the agent to review before entering trades.
|
|
4
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
5
|
+
export async function getTradeFeedbackTool(args, deps) {
|
|
6
|
+
const hours = args.hours ?? 168;
|
|
7
|
+
return fetchIntelApi(`/api/analytics/${enc(args.symbol)}/feedback?hours=${hours}`, deps);
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Tool: get_trade_flow — fetches aggregated trade flow / institutional flow data.
|
|
2
|
+
// Returns buy/sell volume, large trade (whale) activity, net delta, and whale pressure verdict.
|
|
3
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
4
|
+
export async function getTradeFlowTool(args, deps) {
|
|
5
|
+
const minutes = args.minutes ?? 5;
|
|
6
|
+
return fetchIntelApi(`/api/trade-flow/${enc(args.symbol)}?minutes=${minutes}`, deps);
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BinancePublicApi } from '../ccxt/binance-public.js';
|
|
2
|
+
export interface VolumeAnalysisResult {
|
|
3
|
+
symbol: string;
|
|
4
|
+
timeframe: string;
|
|
5
|
+
volumeExpansion: number;
|
|
6
|
+
closePosition: number;
|
|
7
|
+
directionalThrust: number;
|
|
8
|
+
buyVolEstimate: number;
|
|
9
|
+
latestVolume: number;
|
|
10
|
+
avgVolume: number;
|
|
11
|
+
interpretation: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function getVolumeAnalysisTool(args: {
|
|
15
|
+
symbol: string;
|
|
16
|
+
timeframe?: string;
|
|
17
|
+
}, deps: {
|
|
18
|
+
binanceApi: BinancePublicApi;
|
|
19
|
+
}): Promise<VolumeAnalysisResult | {
|
|
20
|
+
error: string;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Tool: get_volume_analysis — volume expansion, directional thrust, close position
|
|
2
|
+
// Fetches 25 bars of 5m OHLCV and computes volume metrics for the latest bar.
|
|
3
|
+
export async function getVolumeAnalysisTool(args, deps) {
|
|
4
|
+
const timeframe = args.timeframe ?? '5m';
|
|
5
|
+
try {
|
|
6
|
+
const candles = await deps.binanceApi.fetchOHLCV(args.symbol, timeframe, 25);
|
|
7
|
+
if (!candles || candles.length < 3) {
|
|
8
|
+
return { error: `Insufficient OHLCV data for ${args.symbol} ${timeframe}` };
|
|
9
|
+
}
|
|
10
|
+
// Filter valid bars (volume > 0)
|
|
11
|
+
const valid = candles.filter((c) => c[5] > 0);
|
|
12
|
+
if (valid.length < 3) {
|
|
13
|
+
return { error: `Insufficient valid bars for ${args.symbol} ${timeframe}` };
|
|
14
|
+
}
|
|
15
|
+
// Latest bar
|
|
16
|
+
const latest = valid[valid.length - 1];
|
|
17
|
+
const [, , latestHigh, latestLow, latestClose, latestVolume] = latest;
|
|
18
|
+
// 20-bar average volume (excluding latest bar)
|
|
19
|
+
const lookback = valid.slice(Math.max(0, valid.length - 21), valid.length - 1);
|
|
20
|
+
const avgVolume = lookback.length > 0
|
|
21
|
+
? lookback.reduce((sum, r) => sum + r[5], 0) / lookback.length
|
|
22
|
+
: latestVolume;
|
|
23
|
+
// Volume expansion ratio
|
|
24
|
+
const volumeExpansion = avgVolume > 0 ? latestVolume / avgVolume : 1;
|
|
25
|
+
// Close position: where close sits in the bar's range (0 = low, 1 = high)
|
|
26
|
+
const range = latestHigh - latestLow;
|
|
27
|
+
const closePosition = range > 0 ? (latestClose - latestLow) / range : 0.5;
|
|
28
|
+
// Directional thrust: combines volume expansion with close position
|
|
29
|
+
// -1 = high volume bearish (close at low), +1 = high volume bullish (close at high)
|
|
30
|
+
const dirMultiplier = (closePosition - 0.5) * 2;
|
|
31
|
+
const cappedExpansion = Math.min(volumeExpansion, 5);
|
|
32
|
+
const directionalThrust = dirMultiplier * Math.min(1, cappedExpansion / 2);
|
|
33
|
+
// Buy volume estimate (proxy)
|
|
34
|
+
const buyVolEstimate = closePosition;
|
|
35
|
+
// Human-readable interpretation
|
|
36
|
+
let interpretation;
|
|
37
|
+
if (volumeExpansion > 2 && directionalThrust > 0.3) {
|
|
38
|
+
interpretation = 'Strong bullish volume surge — buyers aggressive';
|
|
39
|
+
}
|
|
40
|
+
else if (volumeExpansion > 2 && directionalThrust < -0.3) {
|
|
41
|
+
interpretation = 'Strong bearish volume surge — sellers aggressive';
|
|
42
|
+
}
|
|
43
|
+
else if (volumeExpansion > 1.5 && Math.abs(directionalThrust) < 0.15) {
|
|
44
|
+
interpretation = 'High volume indecision — battle between buyers and sellers';
|
|
45
|
+
}
|
|
46
|
+
else if (volumeExpansion < 0.5) {
|
|
47
|
+
interpretation = 'Low volume — lack of participation, potential breakout setup';
|
|
48
|
+
}
|
|
49
|
+
else if (directionalThrust > 0.2) {
|
|
50
|
+
interpretation = 'Moderate bullish bias — close near highs';
|
|
51
|
+
}
|
|
52
|
+
else if (directionalThrust < -0.2) {
|
|
53
|
+
interpretation = 'Moderate bearish bias — close near lows';
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
interpretation = 'Normal volume, neutral positioning';
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
symbol: args.symbol,
|
|
60
|
+
timeframe,
|
|
61
|
+
volumeExpansion: +volumeExpansion.toFixed(2),
|
|
62
|
+
closePosition: +closePosition.toFixed(3),
|
|
63
|
+
directionalThrust: +directionalThrust.toFixed(3),
|
|
64
|
+
buyVolEstimate: +buyVolEstimate.toFixed(3),
|
|
65
|
+
latestVolume: Math.round(latestVolume),
|
|
66
|
+
avgVolume: Math.round(avgVolume),
|
|
67
|
+
interpretation,
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
return { error: `get_volume_analysis failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Tool: get_volume_profile — fetches volume profile from the Intelligence API.
|
|
2
|
+
// Returns price buckets with buy/sell volume, POC, value area, and current price.
|
|
3
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
4
|
+
export async function getVolumeProfileTool(args, deps) {
|
|
5
|
+
const period = args.period ?? 24;
|
|
6
|
+
return fetchIntelApi(`/api/volume-profile/${enc(args.symbol)}?period=${period}`, deps);
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BinancePublicApi } from '../ccxt/binance-public.js';
|
|
2
|
+
import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
|
|
3
|
+
import type { CcxtTicker } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Fetch the latest ticker from Binance and update the simulator.
|
|
6
|
+
* Returns the ticker on success, or an error object on failure.
|
|
7
|
+
*/
|
|
8
|
+
export declare function fetchCurrentPrice(symbol: string, deps: {
|
|
9
|
+
binanceApi: BinancePublicApi;
|
|
10
|
+
simulator: ExchangeSimulator;
|
|
11
|
+
}): Promise<CcxtTicker | {
|
|
12
|
+
error: string;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Fetch the latest order book from Binance and cache in simulator.
|
|
16
|
+
* Best-effort: never throws. Skips fetch if cached book is fresh enough.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fetchOrderBook(symbol: string, deps: {
|
|
19
|
+
binanceApi: BinancePublicApi;
|
|
20
|
+
simulator: ExchangeSimulator;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
/** Type guard: check if the result is an error object */
|
|
23
|
+
export declare function isError(result: unknown): result is {
|
|
24
|
+
error: string;
|
|
25
|
+
};
|
package/tools/helpers.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Shared helpers for tool implementations.
|
|
2
|
+
/**
|
|
3
|
+
* Fetch the latest ticker from Binance and update the simulator.
|
|
4
|
+
* Returns the ticker on success, or an error object on failure.
|
|
5
|
+
*/
|
|
6
|
+
export async function fetchCurrentPrice(symbol, deps) {
|
|
7
|
+
const ticker = await deps.binanceApi.fetchTicker(symbol);
|
|
8
|
+
if (!ticker) {
|
|
9
|
+
return { error: `Failed to fetch current price for ${symbol}` };
|
|
10
|
+
}
|
|
11
|
+
deps.simulator.updateTicker(ticker);
|
|
12
|
+
return ticker;
|
|
13
|
+
}
|
|
14
|
+
/** Default TTL for cached orderbook: 5 seconds. */
|
|
15
|
+
const ORDERBOOK_TTL_MS = 5_000;
|
|
16
|
+
/**
|
|
17
|
+
* Fetch the latest order book from Binance and cache in simulator.
|
|
18
|
+
* Best-effort: never throws. Skips fetch if cached book is fresh enough.
|
|
19
|
+
*/
|
|
20
|
+
export async function fetchOrderBook(symbol, deps) {
|
|
21
|
+
try {
|
|
22
|
+
// Skip if cached book is still fresh
|
|
23
|
+
const cached = deps.simulator.getLastOrderBook(symbol);
|
|
24
|
+
if (cached && Date.now() - cached.timestamp < ORDERBOOK_TTL_MS)
|
|
25
|
+
return;
|
|
26
|
+
const orderbook = await deps.binanceApi.fetchOrderBook(symbol, 20);
|
|
27
|
+
if (orderbook) {
|
|
28
|
+
deps.simulator.updateOrderBook(symbol, orderbook);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Best-effort: fill engine falls back to random slippage
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Type guard: check if the result is an error object */
|
|
36
|
+
export function isError(result) {
|
|
37
|
+
return typeof result === 'object' && result !== null && 'error' in result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface IntelApiDeps {
|
|
2
|
+
connectionToken: string;
|
|
3
|
+
intelligenceUrl: string;
|
|
4
|
+
}
|
|
5
|
+
export interface FetchOptions {
|
|
6
|
+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
7
|
+
body?: unknown;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Encode a value for safe use in a URL path segment */
|
|
11
|
+
export declare const enc: typeof encodeURIComponent;
|
|
12
|
+
export declare function fetchIntelApi(path: string, deps: IntelApiDeps, options?: FetchOptions): Promise<Record<string, unknown> | {
|
|
13
|
+
error: string;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Shared helper for Intelligence API calls.
|
|
2
|
+
// All intelligence tools use Bearer token auth against intel.reefclaw.com.
|
|
3
|
+
/** Encode a value for safe use in a URL path segment */
|
|
4
|
+
export const enc = encodeURIComponent;
|
|
5
|
+
export async function fetchIntelApi(path, deps, options) {
|
|
6
|
+
const { connectionToken, intelligenceUrl } = deps;
|
|
7
|
+
if (!connectionToken) {
|
|
8
|
+
return { error: 'No ReefClaw connection token configured. Intelligence features require a reefclaw.com account.' };
|
|
9
|
+
}
|
|
10
|
+
if (!intelligenceUrl) {
|
|
11
|
+
return { error: 'No intelligence URL configured. Set intelligenceUrl in openclaw.json plugin config.' };
|
|
12
|
+
}
|
|
13
|
+
const url = `${intelligenceUrl}${path}`;
|
|
14
|
+
const headers = { Authorization: `Bearer ${connectionToken}` };
|
|
15
|
+
const fetchInit = {
|
|
16
|
+
method: options?.method ?? 'GET',
|
|
17
|
+
headers,
|
|
18
|
+
signal: AbortSignal.timeout(options?.timeoutMs ?? 20_000),
|
|
19
|
+
};
|
|
20
|
+
if (options?.body !== undefined) {
|
|
21
|
+
headers['Content-Type'] = 'application/json';
|
|
22
|
+
fetchInit.body = JSON.stringify(options.body);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(url, fetchInit);
|
|
26
|
+
if (res.status === 401) {
|
|
27
|
+
return { error: 'Invalid or expired ReefClaw connection token.' };
|
|
28
|
+
}
|
|
29
|
+
if (res.status === 503) {
|
|
30
|
+
return { error: 'Intelligence service not ready. The regime classifier may still be loading.' };
|
|
31
|
+
}
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
// Try to extract the server's error message for actionable feedback
|
|
34
|
+
let detail = `Intelligence API returned ${res.status}`;
|
|
35
|
+
try {
|
|
36
|
+
const errBody = (await res.json());
|
|
37
|
+
if (errBody.error)
|
|
38
|
+
detail = errBody.error;
|
|
39
|
+
}
|
|
40
|
+
catch { /* ignore parse failures */ }
|
|
41
|
+
return { error: detail };
|
|
42
|
+
}
|
|
43
|
+
return (await res.json());
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err instanceof Error && err.name === 'TimeoutError') {
|
|
47
|
+
const timeoutSec = Math.round((options?.timeoutMs ?? 20_000) / 1000);
|
|
48
|
+
return { error: `Intelligence API request timed out (${timeoutSec}s). Try again later.` };
|
|
49
|
+
}
|
|
50
|
+
return { error: `Intelligence API call failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IntelApiDeps } from './intel-api.js';
|
|
2
|
+
import type { SymbolFact, StrategyConfig, StrategyRow } from '../strategy/evaluator.js';
|
|
3
|
+
export declare function getAllFactsCached(deps: IntelApiDeps): Promise<SymbolFact[] | {
|
|
4
|
+
error: string;
|
|
5
|
+
}>;
|
|
6
|
+
/** Look up a single symbol's fact, preferring the shared all-facts snapshot.
|
|
7
|
+
* Falls back to the single-symbol endpoint when the all-facts snapshot is
|
|
8
|
+
* cold or the symbol is missing from it (e.g., a watchlist symbol not in
|
|
9
|
+
* the default 20-pair set). */
|
|
10
|
+
export declare function getFactBySymbolCached(rawSymbol: string, deps: IntelApiDeps): Promise<SymbolFact | {
|
|
11
|
+
error: string;
|
|
12
|
+
}>;
|
|
13
|
+
/** Fetch active strategy rows, caching the full row shape. Both
|
|
14
|
+
* getStrategiesCached and this share the same rows cache so a warm cache
|
|
15
|
+
* from either entry point services the other without a second fetch. */
|
|
16
|
+
export declare function getStrategyRowsCached(deps: IntelApiDeps): Promise<StrategyRow[] | {
|
|
17
|
+
error: string;
|
|
18
|
+
}>;
|
|
19
|
+
/** Backwards-compatible configs-only view, derived from the rows cache. */
|
|
20
|
+
export declare function getStrategiesCached(deps: IntelApiDeps): Promise<StrategyConfig[] | {
|
|
21
|
+
error: string;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const __testing__: {
|
|
24
|
+
resetCaches(): void;
|
|
25
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Shared TTL cache for intelligence facts + strategies.
|
|
2
|
+
//
|
|
3
|
+
// Why this module exists: scan_pairs and get_setup_detail used to maintain
|
|
4
|
+
// independent caches. scan_pairs cached the all-facts endpoint with a 30s
|
|
5
|
+
// TTL; get_setup_detail had no cache and hit the single-symbol endpoint on
|
|
6
|
+
// every call. When the fact-computer recomputed between the two calls,
|
|
7
|
+
// scan_pairs and get_setup_detail saw different facts for the same symbol —
|
|
8
|
+
// regime, chosen strategy, and entry/stop geometry could all diverge. The
|
|
9
|
+
// 2026-05-15 session showed this repeatedly: scan ranks AVAX as TREND_DOWN
|
|
10
|
+
// short, drill-down returns a LONG ladder, agent rejects setup as
|
|
11
|
+
// "directionally inconsistent." Sharing the cache eliminates the divergence.
|
|
12
|
+
//
|
|
13
|
+
// API:
|
|
14
|
+
// getAllFactsCached(deps) — populates the all-facts cache
|
|
15
|
+
// getFactBySymbolCached(symbol, deps) — reads from all-facts cache when
|
|
16
|
+
// warm; otherwise falls back to
|
|
17
|
+
// single-symbol fetch + populates
|
|
18
|
+
// per-symbol cache
|
|
19
|
+
// getStrategiesCached(deps) — strategies cache
|
|
20
|
+
// __testing__.resetCaches() — clears all caches between tests
|
|
21
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
22
|
+
// facts refresh every 5 min server-side; 30s client cache is safe
|
|
23
|
+
const FACTS_TTL_MS = 30_000;
|
|
24
|
+
// strategies are slow-changing user state
|
|
25
|
+
const STRAT_TTL_MS = 60_000;
|
|
26
|
+
let allFactsCache = null;
|
|
27
|
+
// Single rows cache backs BOTH the configs-only and rows views — keeping them
|
|
28
|
+
// independent caused divergent fetches (scan_pairs warmed configs but
|
|
29
|
+
// get_setup_detail's rows lookup missed and re-fetched, returning undefined
|
|
30
|
+
// on test mocks). One cache, two derived views.
|
|
31
|
+
let stratRowCache = null;
|
|
32
|
+
// per-symbol cache for cases where the all-facts cache hasn't been populated
|
|
33
|
+
// in this heartbeat (e.g., the agent called get_setup_detail directly without
|
|
34
|
+
// running scan_pairs first).
|
|
35
|
+
const symbolFactCache = new Map();
|
|
36
|
+
function normalizeSymbol(raw) {
|
|
37
|
+
return raw.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
|
38
|
+
}
|
|
39
|
+
export async function getAllFactsCached(deps) {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (allFactsCache && now < allFactsCache.expiry)
|
|
42
|
+
return allFactsCache.value;
|
|
43
|
+
const resp = await fetchIntelApi('/api/facts', deps, { timeoutMs: 30_000 });
|
|
44
|
+
if (!resp || typeof resp !== 'object') {
|
|
45
|
+
return { error: 'Intelligence /api/facts returned no response.' };
|
|
46
|
+
}
|
|
47
|
+
if ('error' in resp)
|
|
48
|
+
return { error: resp.error };
|
|
49
|
+
const value = (resp.facts ?? []);
|
|
50
|
+
allFactsCache = { value, expiry: now + FACTS_TTL_MS };
|
|
51
|
+
// Seed the per-symbol cache so a subsequent getFactBySymbolCached call
|
|
52
|
+
// for any symbol in this snapshot hits a cache without a round-trip.
|
|
53
|
+
const seedExpiry = now + FACTS_TTL_MS;
|
|
54
|
+
for (const f of value) {
|
|
55
|
+
if (!f.symbol)
|
|
56
|
+
continue;
|
|
57
|
+
symbolFactCache.set(normalizeSymbol(f.symbol), { value: f, expiry: seedExpiry });
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
/** Look up a single symbol's fact, preferring the shared all-facts snapshot.
|
|
62
|
+
* Falls back to the single-symbol endpoint when the all-facts snapshot is
|
|
63
|
+
* cold or the symbol is missing from it (e.g., a watchlist symbol not in
|
|
64
|
+
* the default 20-pair set). */
|
|
65
|
+
export async function getFactBySymbolCached(rawSymbol, deps) {
|
|
66
|
+
const symbol = normalizeSymbol(rawSymbol);
|
|
67
|
+
const now = Date.now();
|
|
68
|
+
// 1. Try the per-symbol cache (seeded by getAllFactsCached or by a prior
|
|
69
|
+
// single-symbol fetch).
|
|
70
|
+
const cached = symbolFactCache.get(symbol);
|
|
71
|
+
if (cached && now < cached.expiry)
|
|
72
|
+
return cached.value;
|
|
73
|
+
// 2. If the all-facts cache is warm and contains the symbol, use it. This
|
|
74
|
+
// is the path that closes the scan_pairs / get_setup_detail divergence —
|
|
75
|
+
// after a heartbeat scan, every drill-down hits the same snapshot the
|
|
76
|
+
// scan saw.
|
|
77
|
+
if (allFactsCache && now < allFactsCache.expiry) {
|
|
78
|
+
const hit = allFactsCache.value.find(f => normalizeSymbol(f.symbol) === symbol);
|
|
79
|
+
if (hit) {
|
|
80
|
+
symbolFactCache.set(symbol, { value: hit, expiry: allFactsCache.expiry });
|
|
81
|
+
return hit;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 3. Fall back to the single-symbol endpoint (symbol not in default scan
|
|
85
|
+
// set, or both caches cold). Populate the per-symbol cache so a repeat
|
|
86
|
+
// call in the same heartbeat is free.
|
|
87
|
+
const resp = await fetchIntelApi(`/api/facts/${enc(symbol)}`, deps, { timeoutMs: 30_000 });
|
|
88
|
+
if (!resp || typeof resp !== 'object') {
|
|
89
|
+
return { error: `Intelligence /api/facts/${symbol} returned no response.` };
|
|
90
|
+
}
|
|
91
|
+
if ('error' in resp)
|
|
92
|
+
return { error: resp.error };
|
|
93
|
+
const fact = resp;
|
|
94
|
+
if (!fact.symbol) {
|
|
95
|
+
return { error: `No facts available for ${symbol}` };
|
|
96
|
+
}
|
|
97
|
+
symbolFactCache.set(symbol, { value: fact, expiry: now + FACTS_TTL_MS });
|
|
98
|
+
return fact;
|
|
99
|
+
}
|
|
100
|
+
/** Fetch active strategy rows, caching the full row shape. Both
|
|
101
|
+
* getStrategiesCached and this share the same rows cache so a warm cache
|
|
102
|
+
* from either entry point services the other without a second fetch. */
|
|
103
|
+
export async function getStrategyRowsCached(deps) {
|
|
104
|
+
const now = Date.now();
|
|
105
|
+
if (stratRowCache && now < stratRowCache.expiry)
|
|
106
|
+
return stratRowCache.value;
|
|
107
|
+
const resp = await fetchIntelApi('/api/strategies?active=true', deps, { timeoutMs: 20_000 });
|
|
108
|
+
// The mocked fetch may return undefined when no more responses are queued;
|
|
109
|
+
// guard explicitly rather than relying on `'error' in resp` (the `in`
|
|
110
|
+
// operator throws on undefined).
|
|
111
|
+
if (!resp || typeof resp !== 'object') {
|
|
112
|
+
return { error: 'Intelligence /api/strategies returned no response.' };
|
|
113
|
+
}
|
|
114
|
+
if ('error' in resp)
|
|
115
|
+
return { error: resp.error };
|
|
116
|
+
const rows = (resp.strategies ?? []);
|
|
117
|
+
stratRowCache = { value: rows, expiry: now + STRAT_TTL_MS };
|
|
118
|
+
return rows;
|
|
119
|
+
}
|
|
120
|
+
/** Backwards-compatible configs-only view, derived from the rows cache. */
|
|
121
|
+
export async function getStrategiesCached(deps) {
|
|
122
|
+
const rows = await getStrategyRowsCached(deps);
|
|
123
|
+
if ('error' in rows)
|
|
124
|
+
return rows;
|
|
125
|
+
return rows.map(r => r.config);
|
|
126
|
+
}
|
|
127
|
+
export const __testing__ = {
|
|
128
|
+
resetCaches() {
|
|
129
|
+
allFactsCache = null;
|
|
130
|
+
stratRowCache = null;
|
|
131
|
+
symbolFactCache.clear();
|
|
132
|
+
},
|
|
133
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IntelApiDeps } from './intel-api.js';
|
|
2
|
+
export interface ListStrategiesArgs {
|
|
3
|
+
active_only?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function listStrategiesTool(args: ListStrategiesArgs, deps: IntelApiDeps): Promise<Record<string, unknown> | {
|
|
6
|
+
error: string;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Tool: list_strategies — list all strategies in the Strategy Box.
|
|
2
|
+
import { fetchIntelApi } from './intel-api.js';
|
|
3
|
+
export async function listStrategiesTool(args, deps) {
|
|
4
|
+
const query = args.active_only ? '?active=true' : '';
|
|
5
|
+
return fetchIntelApi(`/api/strategies${query}`, deps);
|
|
6
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
interface ModifyStopArgs {
|
|
3
|
+
symbol: string;
|
|
4
|
+
new_stop_price: number;
|
|
5
|
+
}
|
|
6
|
+
interface ModifyStopResult {
|
|
7
|
+
ok: true;
|
|
8
|
+
symbol: string;
|
|
9
|
+
old_stop_price: number | null;
|
|
10
|
+
new_stop_price: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function modifyStopTool(args: ModifyStopArgs, deps: {
|
|
13
|
+
adapter: IExchangeAdapter;
|
|
14
|
+
}): Promise<ModifyStopResult | {
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Tool: modify_stop — move the exchange-side stop-loss on an open live
|
|
2
|
+
// position. Implemented as cancel-then-resubmit inside BracketManager, with
|
|
3
|
+
// automatic rollback if the new submit fails.
|
|
4
|
+
//
|
|
5
|
+
// Paper mode: moves the position's metadata.stopPrice in place (the paper
|
|
6
|
+
// stop-watcher reads it), so BE/trail works without a close+reopen round-trip.
|
|
7
|
+
// Live mode without brackets enabled: error asking operator to enable.
|
|
8
|
+
import { validateStopDirection } from '../live/bracket-params.js';
|
|
9
|
+
import { formatError } from '../logger.js';
|
|
10
|
+
export async function modifyStopTool(args, deps) {
|
|
11
|
+
if (!args.symbol)
|
|
12
|
+
return { error: 'symbol is required.' };
|
|
13
|
+
if (!Number.isFinite(args.new_stop_price) || args.new_stop_price <= 0) {
|
|
14
|
+
return { error: 'new_stop_price must be a positive number.' };
|
|
15
|
+
}
|
|
16
|
+
if (!deps.adapter.isLive) {
|
|
17
|
+
return modifyStopPaper(deps.adapter, args);
|
|
18
|
+
}
|
|
19
|
+
const mgr = deps.adapter.getBracketManager?.();
|
|
20
|
+
const ledger = deps.adapter.getBracketLedger?.();
|
|
21
|
+
if (!mgr || !ledger) {
|
|
22
|
+
return { error: 'Bracket orders are not enabled. Set brackets.mode in plugin-config.json to "observe" or "enforce".' };
|
|
23
|
+
}
|
|
24
|
+
const entry = ledger.getBySymbol(args.symbol);
|
|
25
|
+
if (!entry) {
|
|
26
|
+
return { error: `No active bracket for ${args.symbol}.` };
|
|
27
|
+
}
|
|
28
|
+
// Direction check — catch flipped sign before burning a cancel+resubmit cycle
|
|
29
|
+
const refPrice = entry.stopPrice && entry.targetPrice
|
|
30
|
+
? (entry.stopPrice + entry.targetPrice) / 2
|
|
31
|
+
: await deps.adapter.getLastPrice(args.symbol);
|
|
32
|
+
if (refPrice != null && refPrice > 0) {
|
|
33
|
+
const msg = validateStopDirection(entry.entrySide, refPrice, args.new_stop_price);
|
|
34
|
+
if (msg)
|
|
35
|
+
return { error: msg };
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
await mgr.modifyStop(args.symbol, args.new_stop_price);
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
symbol: args.symbol,
|
|
42
|
+
old_stop_price: entry.stopPrice ?? null,
|
|
43
|
+
new_stop_price: args.new_stop_price,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return { error: formatError(err) };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Paper: move the stop on the open position's metadata in place. Validates the
|
|
51
|
+
* same direction geometry live does (stop on the losing side of the current
|
|
52
|
+
* mark), then writes metadata.stopPrice — no fee, no entry/R reset. */
|
|
53
|
+
function modifyStopPaper(adapter, args) {
|
|
54
|
+
const sim = adapter.getSimulator();
|
|
55
|
+
const pos = sim.getPositions(args.symbol)[0];
|
|
56
|
+
if (!pos) {
|
|
57
|
+
return { error: `No open position for ${args.symbol}.` };
|
|
58
|
+
}
|
|
59
|
+
// Validate against the current mark (permits breakeven / trailing — the stop
|
|
60
|
+
// just has to stay on the losing side of where price is now).
|
|
61
|
+
const refPrice = pos.markPrice;
|
|
62
|
+
const entrySide = pos.side === 'long' ? 'buy' : 'sell';
|
|
63
|
+
if (Number.isFinite(refPrice) && refPrice > 0) {
|
|
64
|
+
const msg = validateStopDirection(entrySide, refPrice, args.new_stop_price);
|
|
65
|
+
if (msg)
|
|
66
|
+
return { error: msg };
|
|
67
|
+
}
|
|
68
|
+
const oldStop = pos.stopPrice ?? null;
|
|
69
|
+
try {
|
|
70
|
+
sim.updatePositionMetadata(args.symbol, { stopPrice: args.new_stop_price });
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return { error: formatError(err) };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
ok: true,
|
|
77
|
+
symbol: args.symbol,
|
|
78
|
+
old_stop_price: oldStop,
|
|
79
|
+
new_stop_price: args.new_stop_price,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
interface ModifyTargetArgs {
|
|
3
|
+
symbol: string;
|
|
4
|
+
new_target_price: number;
|
|
5
|
+
}
|
|
6
|
+
interface ModifyTargetResult {
|
|
7
|
+
ok: true;
|
|
8
|
+
symbol: string;
|
|
9
|
+
old_target_price: number | null;
|
|
10
|
+
new_target_price: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function modifyTargetTool(args: ModifyTargetArgs, deps: {
|
|
13
|
+
adapter: IExchangeAdapter;
|
|
14
|
+
}): Promise<ModifyTargetResult | {
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Tool: modify_target — move the exchange-side take-profit on an open live
|
|
2
|
+
// position. Mirror of modify_stop; see that file for rationale.
|
|
3
|
+
import { validateTargetDirection } from '../live/bracket-params.js';
|
|
4
|
+
import { formatError } from '../logger.js';
|
|
5
|
+
export async function modifyTargetTool(args, deps) {
|
|
6
|
+
if (!args.symbol)
|
|
7
|
+
return { error: 'symbol is required.' };
|
|
8
|
+
if (!Number.isFinite(args.new_target_price) || args.new_target_price <= 0) {
|
|
9
|
+
return { error: 'new_target_price must be a positive number.' };
|
|
10
|
+
}
|
|
11
|
+
if (!deps.adapter.isLive) {
|
|
12
|
+
return modifyTargetPaper(deps.adapter, args);
|
|
13
|
+
}
|
|
14
|
+
const mgr = deps.adapter.getBracketManager?.();
|
|
15
|
+
const ledger = deps.adapter.getBracketLedger?.();
|
|
16
|
+
if (!mgr || !ledger) {
|
|
17
|
+
return { error: 'Bracket orders are not enabled. Set brackets.mode in plugin-config.json to "observe" or "enforce".' };
|
|
18
|
+
}
|
|
19
|
+
const entry = ledger.getBySymbol(args.symbol);
|
|
20
|
+
if (!entry) {
|
|
21
|
+
return { error: `No active bracket for ${args.symbol}.` };
|
|
22
|
+
}
|
|
23
|
+
const refPrice = entry.stopPrice && entry.targetPrice
|
|
24
|
+
? (entry.stopPrice + entry.targetPrice) / 2
|
|
25
|
+
: await deps.adapter.getLastPrice(args.symbol);
|
|
26
|
+
if (refPrice != null && refPrice > 0) {
|
|
27
|
+
const msg = validateTargetDirection(entry.entrySide, refPrice, args.new_target_price);
|
|
28
|
+
if (msg)
|
|
29
|
+
return { error: msg };
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
await mgr.modifyTarget(args.symbol, args.new_target_price);
|
|
33
|
+
return {
|
|
34
|
+
ok: true,
|
|
35
|
+
symbol: args.symbol,
|
|
36
|
+
old_target_price: entry.targetPrice ?? null,
|
|
37
|
+
new_target_price: args.new_target_price,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return { error: formatError(err) };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Paper: move the take-profit on the open position's metadata in place. */
|
|
45
|
+
function modifyTargetPaper(adapter, args) {
|
|
46
|
+
const sim = adapter.getSimulator();
|
|
47
|
+
const pos = sim.getPositions(args.symbol)[0];
|
|
48
|
+
if (!pos) {
|
|
49
|
+
return { error: `No open position for ${args.symbol}.` };
|
|
50
|
+
}
|
|
51
|
+
const refPrice = pos.markPrice;
|
|
52
|
+
const entrySide = pos.side === 'long' ? 'buy' : 'sell';
|
|
53
|
+
if (Number.isFinite(refPrice) && refPrice > 0) {
|
|
54
|
+
const msg = validateTargetDirection(entrySide, refPrice, args.new_target_price);
|
|
55
|
+
if (msg)
|
|
56
|
+
return { error: msg };
|
|
57
|
+
}
|
|
58
|
+
const oldTarget = pos.targetPrice ?? null;
|
|
59
|
+
try {
|
|
60
|
+
sim.updatePositionMetadata(args.symbol, { targetPrice: args.new_target_price });
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return { error: formatError(err) };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
symbol: args.symbol,
|
|
68
|
+
old_target_price: oldTarget,
|
|
69
|
+
new_target_price: args.new_target_price,
|
|
70
|
+
};
|
|
71
|
+
}
|