@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,29 @@
|
|
|
1
|
+
import type { IntelApiDeps } from './intel-api.js';
|
|
2
|
+
/** Matches simulator Position type */
|
|
3
|
+
interface SimPosition {
|
|
4
|
+
symbol: string;
|
|
5
|
+
side: 'long' | 'short';
|
|
6
|
+
entryPrice: number;
|
|
7
|
+
quantity: number;
|
|
8
|
+
}
|
|
9
|
+
/** Matches simulator Wallet shape */
|
|
10
|
+
interface SimWallet {
|
|
11
|
+
[currency: string]: {
|
|
12
|
+
total: number;
|
|
13
|
+
available: number;
|
|
14
|
+
locked: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface RiskScenarioDeps {
|
|
18
|
+
getPositions(): SimPosition[];
|
|
19
|
+
getWallet(): SimWallet;
|
|
20
|
+
getLastPrice(symbol: string): number | null;
|
|
21
|
+
intelApiDeps: IntelApiDeps;
|
|
22
|
+
}
|
|
23
|
+
export declare function getRiskScenarioTool(args: {
|
|
24
|
+
symbol: string;
|
|
25
|
+
moves?: string;
|
|
26
|
+
}, deps: RiskScenarioDeps): Promise<Record<string, unknown> | {
|
|
27
|
+
error: string;
|
|
28
|
+
}>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Tool: get_risk_scenario — stress test current positions against hypothetical price moves.
|
|
2
|
+
// Fetches positions + balance from simulator state, then calls Intelligence API for scenario analysis.
|
|
3
|
+
// Returns: per-scenario P&L, heat, drawdown zone, stops triggered, limits breached.
|
|
4
|
+
import { enc } from './intel-api.js';
|
|
5
|
+
import { fetchIntelApi } from './intel-api.js';
|
|
6
|
+
export async function getRiskScenarioTool(args, deps) {
|
|
7
|
+
const positions = deps.getPositions();
|
|
8
|
+
const wallet = deps.getWallet();
|
|
9
|
+
const balance = wallet.USDT?.total ?? 0;
|
|
10
|
+
if (balance <= 0) {
|
|
11
|
+
return { error: 'No balance found. Cannot compute risk scenarios.' };
|
|
12
|
+
}
|
|
13
|
+
// Build positions for the API
|
|
14
|
+
const apiPositions = positions.map(pos => {
|
|
15
|
+
const currentPrice = deps.getLastPrice(pos.symbol) ?? pos.entryPrice;
|
|
16
|
+
const side = pos.side === 'long' ? 'LONG' : 'SHORT';
|
|
17
|
+
// Default stop: 2% from entry (no stop data in simulator positions)
|
|
18
|
+
const stopPrice = side === 'LONG' ? pos.entryPrice * 0.98 : pos.entryPrice * 1.02;
|
|
19
|
+
return {
|
|
20
|
+
symbol: pos.symbol,
|
|
21
|
+
side,
|
|
22
|
+
entryPrice: pos.entryPrice,
|
|
23
|
+
currentPrice,
|
|
24
|
+
stopPrice,
|
|
25
|
+
quantity: pos.quantity,
|
|
26
|
+
notional: pos.quantity * currentPrice,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
// Parse custom moves if provided
|
|
30
|
+
let scenarios;
|
|
31
|
+
if (args.moves) {
|
|
32
|
+
scenarios = args.moves.split(',').map(s => ({
|
|
33
|
+
movePct: parseFloat(s.trim()),
|
|
34
|
+
})).filter(s => !isNaN(s.movePct));
|
|
35
|
+
}
|
|
36
|
+
// Normalize symbol for intelligence API (BTC/USDT → BTCUSDT)
|
|
37
|
+
const intelSymbol = args.symbol.replace('/', '');
|
|
38
|
+
const body = {
|
|
39
|
+
positions: apiPositions,
|
|
40
|
+
balance,
|
|
41
|
+
...(scenarios && scenarios.length > 0 && { scenarios }),
|
|
42
|
+
};
|
|
43
|
+
return fetchIntelApi(`/api/risk-scenario/${enc(intelSymbol)}`, deps.intelApiDeps, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
body,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ExchangeSimulator } from '../simulator/exchange-simulator.js';
|
|
2
|
+
import type { ShadowTracker } from '../shadow/shadow-tracker.js';
|
|
3
|
+
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
4
|
+
import type { TradingMode } from '../types.js';
|
|
5
|
+
export interface RiskSummaryResult {
|
|
6
|
+
tradingMode: TradingMode;
|
|
7
|
+
totalEquity: number;
|
|
8
|
+
availableBalance: number;
|
|
9
|
+
lockedBalance: number;
|
|
10
|
+
grossExposure: number;
|
|
11
|
+
grossExposurePercent: number;
|
|
12
|
+
netExposure: number;
|
|
13
|
+
netExposurePercent: number;
|
|
14
|
+
positionCount: number;
|
|
15
|
+
unrealizedPnl: number;
|
|
16
|
+
unrealizedPnlPercent: number;
|
|
17
|
+
positions: Array<{
|
|
18
|
+
symbol: string;
|
|
19
|
+
side: 'long' | 'short';
|
|
20
|
+
quantity: number;
|
|
21
|
+
entryPrice: number;
|
|
22
|
+
markPrice: number;
|
|
23
|
+
notional: number;
|
|
24
|
+
unrealizedPnl: number;
|
|
25
|
+
portfolioPercent: number;
|
|
26
|
+
}>;
|
|
27
|
+
heatScore: number;
|
|
28
|
+
heatLevel: 'low' | 'moderate' | 'elevated' | 'high' | 'critical';
|
|
29
|
+
executionStats?: {
|
|
30
|
+
totalTrades: number;
|
|
31
|
+
avgSlippageBps: number;
|
|
32
|
+
worstSlippageBps: number;
|
|
33
|
+
totalFeesPaid: number;
|
|
34
|
+
avgLatencyMs: number;
|
|
35
|
+
};
|
|
36
|
+
shadowMetrics?: {
|
|
37
|
+
totalShadowTrades: number;
|
|
38
|
+
avgDeltaBps: number;
|
|
39
|
+
worstDeltaBps: number;
|
|
40
|
+
tradingDays: number;
|
|
41
|
+
readyForUpgrade: boolean;
|
|
42
|
+
upgradeBlockers: string[];
|
|
43
|
+
};
|
|
44
|
+
timestamp: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function getRiskSummaryTool(_args: Record<string, never>, deps: {
|
|
47
|
+
simulator: ExchangeSimulator;
|
|
48
|
+
tradingMode?: TradingMode;
|
|
49
|
+
shadowTracker?: ShadowTracker | null;
|
|
50
|
+
adapter?: IExchangeAdapter;
|
|
51
|
+
}): Promise<RiskSummaryResult>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Tool: get_risk_summary — current portfolio risk metrics
|
|
2
|
+
// Returns exposure, drawdown, heat score, position count — everything
|
|
3
|
+
// the agent needs for risk-aware decision making.
|
|
4
|
+
import { getQuoteBalance, getQuoteWalletBalance } from '../balance-utils.js';
|
|
5
|
+
export async function getRiskSummaryTool(_args, deps) {
|
|
6
|
+
// In live modes, use real exchange data instead of simulator
|
|
7
|
+
let balance;
|
|
8
|
+
let positions;
|
|
9
|
+
if (deps.adapter?.isLive) {
|
|
10
|
+
[balance, positions] = await Promise.all([
|
|
11
|
+
deps.adapter.getBalance(),
|
|
12
|
+
deps.adapter.getPositions(),
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
balance = deps.simulator.getBalance();
|
|
17
|
+
positions = deps.simulator.getPositions();
|
|
18
|
+
}
|
|
19
|
+
// Wallet values — totalEquity uses BNFCR-aware reader for Binance Futures
|
|
20
|
+
// multi-asset accounts; free/used stay map-based (those are derived
|
|
21
|
+
// cross-margin numbers that don't compose the same way).
|
|
22
|
+
const totalEquity = getQuoteWalletBalance(balance);
|
|
23
|
+
const availableBalance = getQuoteBalance(balance.free);
|
|
24
|
+
const lockedBalance = getQuoteBalance(balance.used);
|
|
25
|
+
// Exposure calculations
|
|
26
|
+
let grossExposure = 0;
|
|
27
|
+
let longExposure = 0;
|
|
28
|
+
let shortExposure = 0;
|
|
29
|
+
let totalUnrealizedPnl = 0;
|
|
30
|
+
const positionDetails = positions.map((pos) => {
|
|
31
|
+
const notional = Math.abs(pos.notional);
|
|
32
|
+
grossExposure += notional;
|
|
33
|
+
if (pos.side === 'long')
|
|
34
|
+
longExposure += notional;
|
|
35
|
+
else
|
|
36
|
+
shortExposure += notional;
|
|
37
|
+
totalUnrealizedPnl += pos.unrealizedPnl;
|
|
38
|
+
return {
|
|
39
|
+
symbol: pos.symbol,
|
|
40
|
+
side: pos.side,
|
|
41
|
+
quantity: pos.contracts,
|
|
42
|
+
entryPrice: +pos.entryPrice.toFixed(2),
|
|
43
|
+
markPrice: +pos.markPrice.toFixed(2),
|
|
44
|
+
notional: +notional.toFixed(2),
|
|
45
|
+
unrealizedPnl: +pos.unrealizedPnl.toFixed(2),
|
|
46
|
+
portfolioPercent: totalEquity > 0 ? +((notional / totalEquity) * 100).toFixed(1) : 0,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
const netExposure = longExposure - shortExposure;
|
|
50
|
+
const grossExposurePercent = totalEquity > 0 ? (grossExposure / totalEquity) * 100 : 0;
|
|
51
|
+
const netExposurePercent = totalEquity > 0 ? (netExposure / totalEquity) * 100 : 0;
|
|
52
|
+
const unrealizedPnlPercent = totalEquity > 0 ? (totalUnrealizedPnl / totalEquity) * 100 : 0;
|
|
53
|
+
// Heat score: 0-100 composite
|
|
54
|
+
// Components: gross exposure (40%), unrealized loss (30%), position concentration (30%)
|
|
55
|
+
const exposureHeat = Math.min(grossExposurePercent / 100, 1) * 40; // 100% exposure = 40 heat
|
|
56
|
+
const lossHeat = totalUnrealizedPnl < 0
|
|
57
|
+
? Math.min(Math.abs(unrealizedPnlPercent) / 5, 1) * 30 // 5% loss = 30 heat
|
|
58
|
+
: 0;
|
|
59
|
+
const concentrationHeat = positions.length > 0
|
|
60
|
+
? Math.min(positions.length / 5, 1) * 30 // 5+ positions = 30 heat
|
|
61
|
+
: 0;
|
|
62
|
+
const heatScore = Math.round(exposureHeat + lossHeat + concentrationHeat);
|
|
63
|
+
let heatLevel;
|
|
64
|
+
if (heatScore >= 80)
|
|
65
|
+
heatLevel = 'critical';
|
|
66
|
+
else if (heatScore >= 60)
|
|
67
|
+
heatLevel = 'high';
|
|
68
|
+
else if (heatScore >= 40)
|
|
69
|
+
heatLevel = 'elevated';
|
|
70
|
+
else if (heatScore >= 20)
|
|
71
|
+
heatLevel = 'moderate';
|
|
72
|
+
else
|
|
73
|
+
heatLevel = 'low';
|
|
74
|
+
// Execution quality stats (Phase 9a)
|
|
75
|
+
const execStats = deps.simulator.getExecutionStats();
|
|
76
|
+
const executionStats = execStats && execStats.totalTrades > 0
|
|
77
|
+
? {
|
|
78
|
+
totalTrades: execStats.totalTrades,
|
|
79
|
+
avgSlippageBps: +execStats.avgSlippageBps.toFixed(2),
|
|
80
|
+
worstSlippageBps: +execStats.worstSlippageBps.toFixed(2),
|
|
81
|
+
totalFeesPaid: +execStats.totalFeesPaid.toFixed(2),
|
|
82
|
+
avgLatencyMs: +execStats.avgLatencyMs.toFixed(0),
|
|
83
|
+
}
|
|
84
|
+
: undefined;
|
|
85
|
+
// Shadow metrics (Phase 9b)
|
|
86
|
+
let shadowMetrics;
|
|
87
|
+
if (deps.shadowTracker) {
|
|
88
|
+
const sm = deps.shadowTracker.getMetrics();
|
|
89
|
+
const upgrade = deps.shadowTracker.isReadyForUpgrade();
|
|
90
|
+
shadowMetrics = {
|
|
91
|
+
totalShadowTrades: sm.totalShadowTrades,
|
|
92
|
+
avgDeltaBps: +sm.avgDeltaBps.toFixed(2),
|
|
93
|
+
worstDeltaBps: +sm.worstDeltaBps.toFixed(2),
|
|
94
|
+
tradingDays: sm.tradingDays,
|
|
95
|
+
readyForUpgrade: upgrade.ready,
|
|
96
|
+
upgradeBlockers: upgrade.reasons,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
tradingMode: deps.tradingMode ?? 'PAPER',
|
|
101
|
+
totalEquity: +totalEquity.toFixed(2),
|
|
102
|
+
availableBalance: +availableBalance.toFixed(2),
|
|
103
|
+
lockedBalance: +lockedBalance.toFixed(2),
|
|
104
|
+
grossExposure: +grossExposure.toFixed(2),
|
|
105
|
+
grossExposurePercent: +grossExposurePercent.toFixed(1),
|
|
106
|
+
netExposure: +netExposure.toFixed(2),
|
|
107
|
+
netExposurePercent: +netExposurePercent.toFixed(1),
|
|
108
|
+
positionCount: positions.length,
|
|
109
|
+
unrealizedPnl: +totalUnrealizedPnl.toFixed(2),
|
|
110
|
+
unrealizedPnlPercent: +unrealizedPnlPercent.toFixed(2),
|
|
111
|
+
positions: positionDetails,
|
|
112
|
+
heatScore,
|
|
113
|
+
heatLevel,
|
|
114
|
+
executionStats,
|
|
115
|
+
shadowMetrics,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Tool: get_sentiment — free Fear & Greed Index + BTC dominance + global market data.
|
|
2
|
+
// No Pro subscription required. Calls free public APIs via the intelligence service.
|
|
3
|
+
import { fetchIntelApi } from './intel-api.js';
|
|
4
|
+
export async function getSentimentTool(_args, deps) {
|
|
5
|
+
return fetchIntelApi('/api/sentiment', deps);
|
|
6
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Tool: get_session_review — automated end-of-session performance summary.
|
|
2
|
+
// Calls Intelligence API for trade stats, setup/regime breakdown, lessons, and session-over-session comparison.
|
|
3
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
4
|
+
export async function getSessionReviewTool(args, deps) {
|
|
5
|
+
const intelSymbol = args.symbol.replace('/', '');
|
|
6
|
+
const hours = args.hours ?? 24;
|
|
7
|
+
return fetchIntelApi(`/api/session-review/${enc(intelSymbol)}?hours=${hours}`, deps);
|
|
8
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// Tool: get_setup_detail — detailed setup info for a specific symbol.
|
|
2
|
+
// Fetches the symbol's fact, evaluates best-matching strategy,
|
|
3
|
+
// computes entry/stop/targets, returns full detail (~500 tokens).
|
|
4
|
+
//
|
|
5
|
+
// 2026-05-15 hardening: this tool used to (a) silently zero entry/stop/target
|
|
6
|
+
// when `currentPrice` or `atr14` were missing from the fact (the 1000PEPE
|
|
7
|
+
// "zeroed levels" pattern, ~5 occurrences in one session) and (b) always
|
|
8
|
+
// compute a LONG-style ladder regardless of the strategy's directionRule
|
|
9
|
+
// (the AVAX/SUI/TIA "TREND_DOWN label + LONG geometry" pattern). Both surface
|
|
10
|
+
// as the agent rejecting setups as "data integrity failure" or "directionally
|
|
11
|
+
// inconsistent" and standing down. Now we (1) refuse-to-emit when key
|
|
12
|
+
// indicators are missing, (2) resolve direction via the strategy's
|
|
13
|
+
// directionRule + fact context, and (3) compute stop/target geometry from
|
|
14
|
+
// that direction so long and short setups produce ladders that match.
|
|
15
|
+
// Cache is shared with scan_pairs (intel-cache.ts) so drill-down sees the
|
|
16
|
+
// same fact snapshot the scan ranked the symbol from.
|
|
17
|
+
import { evaluateStrategy, } from '../strategy/evaluator.js';
|
|
18
|
+
import { getFactBySymbolCached, getStrategyRowsCached } from './intel-cache.js';
|
|
19
|
+
export async function getSetupDetailTool(args, deps) {
|
|
20
|
+
const symbol = args.symbol.toUpperCase().replace(/[^A-Z0-9]/g, '');
|
|
21
|
+
// 1. Fetch fact for this symbol — shared cache with scan_pairs, so a
|
|
22
|
+
// drill-down after a heartbeat scan hits the same snapshot the scan
|
|
23
|
+
// ranked the symbol from.
|
|
24
|
+
const factRes = await getFactBySymbolCached(symbol, deps);
|
|
25
|
+
if ('error' in factRes)
|
|
26
|
+
return factRes;
|
|
27
|
+
const fact = factRes;
|
|
28
|
+
if (!fact.symbol) {
|
|
29
|
+
return { error: `No facts available for ${symbol}` };
|
|
30
|
+
}
|
|
31
|
+
// 2. Fetch user's active strategy rows (cached, shared with scan_pairs).
|
|
32
|
+
const rowsRes = await getStrategyRowsCached(deps);
|
|
33
|
+
if ('error' in rowsRes)
|
|
34
|
+
return rowsRes;
|
|
35
|
+
const strategyRows = rowsRes;
|
|
36
|
+
// 3. Evaluate all strategies, pick best match
|
|
37
|
+
let bestResult = null;
|
|
38
|
+
let bestScore = -1;
|
|
39
|
+
for (const row of strategyRows) {
|
|
40
|
+
const result = evaluateStrategy(row.config, fact);
|
|
41
|
+
if (result && result.score > bestScore) {
|
|
42
|
+
bestScore = result.score;
|
|
43
|
+
bestResult = result;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Validate indicators BEFORE branching on bestResult — even the "no setup"
|
|
47
|
+
// path returns price, and a zero/missing price is misleading.
|
|
48
|
+
const price = readNumericIndicator(fact, 'currentPrice');
|
|
49
|
+
if (price === null) {
|
|
50
|
+
return {
|
|
51
|
+
error: `Fact for ${symbol} is missing currentPrice — intelligence fact-computer ` +
|
|
52
|
+
`may still be warming up, or this symbol has no recent klines. Retry on ` +
|
|
53
|
+
`next heartbeat; do not size a trade off this payload.`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!bestResult) {
|
|
57
|
+
return {
|
|
58
|
+
symbol,
|
|
59
|
+
price,
|
|
60
|
+
regime: fact.regime,
|
|
61
|
+
regime_confidence: fact.regime_confidence,
|
|
62
|
+
setup: 'none',
|
|
63
|
+
note: 'No active strategy matches this symbol\'s current conditions.',
|
|
64
|
+
indicators: buildIndicatorSummary(fact),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// 4. Validate that the inputs needed for stop/target geometry are present.
|
|
68
|
+
const atr = readNumericIndicator(fact, 'atr14');
|
|
69
|
+
if (atr === null || atr <= 0) {
|
|
70
|
+
return {
|
|
71
|
+
error: `Fact for ${symbol} is missing atr14 (got ${atr ?? 'undefined'}) — cannot ` +
|
|
72
|
+
`compute stop/target geometry without ATR. Retry on next heartbeat.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// 5. Resolve direction from the strategy's directionRule. Refuse to emit a
|
|
76
|
+
// setup when direction is ambiguous — better to return no setup than to
|
|
77
|
+
// fabricate a long ladder for what might be a short trade.
|
|
78
|
+
const strategyConfig = strategyRows.find(r => r.config.name === bestResult.strategy)?.config;
|
|
79
|
+
if (!strategyConfig) {
|
|
80
|
+
return {
|
|
81
|
+
error: `Internal: strategy ${bestResult.strategy} matched but its config ` +
|
|
82
|
+
`disappeared between scan and drill-down. Retry on next heartbeat.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const direction = resolveDirection(strategyConfig, fact);
|
|
86
|
+
if (direction === null) {
|
|
87
|
+
return {
|
|
88
|
+
symbol: fact.symbol,
|
|
89
|
+
price,
|
|
90
|
+
regime: fact.regime,
|
|
91
|
+
regime_confidence: fact.regime_confidence,
|
|
92
|
+
strategy: bestResult.strategy,
|
|
93
|
+
score: bestResult.score,
|
|
94
|
+
setup: 'ambiguous_direction',
|
|
95
|
+
note: `Strategy ${bestResult.strategy} matched but its directionRule ` +
|
|
96
|
+
`(${strategyConfig.directionRule}) could not resolve a clean long/short ` +
|
|
97
|
+
`from the current fact (regime=${fact.regime}). Skip — wait for cleaner ` +
|
|
98
|
+
`context or run score_setup with an explicit direction if you want to force the trade.`,
|
|
99
|
+
indicators: buildIndicatorSummary(fact),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// 6. Compute entry/stop/targets from the matching strategy + direction.
|
|
103
|
+
const entry = computeEntry(strategyConfig, fact, atr, price);
|
|
104
|
+
const stop = computeStop(strategyConfig, atr, entry.price, direction);
|
|
105
|
+
const risk = Math.abs(entry.price - stop.price);
|
|
106
|
+
// Sanity check: if risk collapsed to zero (rounding pathology, weird ATR),
|
|
107
|
+
// refuse rather than emit zeroed targets that look executable.
|
|
108
|
+
if (!Number.isFinite(risk) || risk <= 0) {
|
|
109
|
+
return {
|
|
110
|
+
error: `Computed risk for ${symbol} is ${risk} — entry=${entry.price} stop=${stop.price} ` +
|
|
111
|
+
`collapsed to a zero-risk ladder. Likely cause: ATR=${atr} too small relative to ` +
|
|
112
|
+
`rounding precision. Skip this symbol.`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const sign = direction === 'long' ? 1 : -1;
|
|
116
|
+
const targets = (strategyConfig.targetRMultiples ?? [1.5, 2.5]).map(r => ({
|
|
117
|
+
price: roundPrice(entry.price + risk * r * sign),
|
|
118
|
+
r_multiple: r,
|
|
119
|
+
}));
|
|
120
|
+
// 7. Build risks list
|
|
121
|
+
const risks = [];
|
|
122
|
+
const ind = fact.indicators;
|
|
123
|
+
if (readNumericIndicator(fact, 'obImbalance') === 0.5) {
|
|
124
|
+
risks.push('Orderbook neutral - no clear bid/ask dominance');
|
|
125
|
+
}
|
|
126
|
+
const bb = ind.bb;
|
|
127
|
+
if (bb && bb.bandwidth && bb.bandwidth > 6) {
|
|
128
|
+
risks.push(`High volatility - BB bandwidth ${bb.bandwidth.toFixed(1)}%`);
|
|
129
|
+
}
|
|
130
|
+
const fundingZ = readNumericIndicator(fact, 'fundingZScore');
|
|
131
|
+
if (fundingZ !== null && Math.abs(fundingZ) > 2) {
|
|
132
|
+
risks.push(`Elevated funding z-score: ${fundingZ.toFixed(1)}σ`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
symbol: fact.symbol,
|
|
136
|
+
price,
|
|
137
|
+
regime: fact.regime,
|
|
138
|
+
regime_confidence: fact.regime_confidence,
|
|
139
|
+
strategy: bestResult.strategy,
|
|
140
|
+
direction,
|
|
141
|
+
score: bestResult.score,
|
|
142
|
+
entry: { price: entry.price, type: entry.type, rationale: entry.rationale },
|
|
143
|
+
stop: { price: stop.price, type: stop.type, rationale: stop.rationale },
|
|
144
|
+
targets,
|
|
145
|
+
indicators: buildIndicatorSummary(fact),
|
|
146
|
+
conditions: `${bestResult.conditionsMet}/${bestResult.conditionsTotal} met`,
|
|
147
|
+
risks: risks.length > 0 ? risks : ['No significant risks identified'],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// ─── Helpers ────────────────────────────────────────────────────────────
|
|
151
|
+
/** Read an indicator as a finite number. Returns null when the value is
|
|
152
|
+
* missing, NaN, or non-finite — callers MUST handle null explicitly rather
|
|
153
|
+
* than treating it as zero (the pre-fix `?? 0` behaviour was the root cause
|
|
154
|
+
* of the 1000PEPE zeroed-levels bug). */
|
|
155
|
+
function readNumericIndicator(fact, key) {
|
|
156
|
+
const raw = fact.indicators[key];
|
|
157
|
+
if (typeof raw === 'number' && Number.isFinite(raw))
|
|
158
|
+
return raw;
|
|
159
|
+
if (typeof raw === 'string') {
|
|
160
|
+
const n = Number(raw);
|
|
161
|
+
if (Number.isFinite(n))
|
|
162
|
+
return n;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
/** Resolve a clean long/short for a strategy given the current fact. Returns
|
|
167
|
+
* null when the directionRule cannot be unambiguously satisfied — the caller
|
|
168
|
+
* must refuse to emit a setup in that case rather than guess. */
|
|
169
|
+
function resolveDirection(strategy, fact) {
|
|
170
|
+
switch (strategy.directionRule) {
|
|
171
|
+
case 'fixed_long':
|
|
172
|
+
return 'long';
|
|
173
|
+
case 'fixed_short':
|
|
174
|
+
return 'short';
|
|
175
|
+
case 'from_regime': {
|
|
176
|
+
const r = (fact.regime || '').toUpperCase();
|
|
177
|
+
if (r === 'TREND_UP')
|
|
178
|
+
return 'long';
|
|
179
|
+
if (r === 'TREND_DOWN')
|
|
180
|
+
return 'short';
|
|
181
|
+
// RANGE_TIGHT / RANGE_WIDE / VOLATILITY_EXPANSION / UNKNOWN: ambiguous.
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
case 'from_funding': {
|
|
185
|
+
const z = readNumericIndicator(fact, 'fundingZScore');
|
|
186
|
+
if (z === null)
|
|
187
|
+
return null;
|
|
188
|
+
// Crowded longs (positive z) → contrarian short; crowded shorts → long.
|
|
189
|
+
if (z >= 1)
|
|
190
|
+
return 'short';
|
|
191
|
+
if (z <= -1)
|
|
192
|
+
return 'long';
|
|
193
|
+
return null; // Z between -1 and 1: no edge.
|
|
194
|
+
}
|
|
195
|
+
case 'from_sweep': {
|
|
196
|
+
// The sweep direction lives inside the price_sweep condition's metadata,
|
|
197
|
+
// which evaluateStrategy doesn't surface to us as a single field. Without
|
|
198
|
+
// that signal we'd be guessing; refuse to emit and let the agent run
|
|
199
|
+
// score_setup with an explicit direction.
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function buildIndicatorSummary(fact) {
|
|
207
|
+
const ind = fact.indicators;
|
|
208
|
+
const macd = ind.macd;
|
|
209
|
+
const stochRsi = ind.stochRsi;
|
|
210
|
+
const supertrend = ind.supertrend;
|
|
211
|
+
const price = readNumericIndicator(fact, 'currentPrice');
|
|
212
|
+
const vwap = readNumericIndicator(fact, 'vwap');
|
|
213
|
+
const fundingZ = readNumericIndicator(fact, 'fundingZScore');
|
|
214
|
+
const oi = readNumericIndicator(fact, 'oiRatio');
|
|
215
|
+
return {
|
|
216
|
+
rsi: ind.rsi14,
|
|
217
|
+
adx: ind.adx,
|
|
218
|
+
macd: macd?.crossover && macd.crossover !== 'none'
|
|
219
|
+
? `${macd.crossover} cross`
|
|
220
|
+
: `histogram ${macd?.histogram ?? 0}`,
|
|
221
|
+
vwap: price !== null && vwap !== null && vwap !== 0
|
|
222
|
+
? `price ${price > vwap ? 'above' : 'below'} (${((price - vwap) / vwap * 100).toFixed(2)}%)`
|
|
223
|
+
: 'vwap unavailable',
|
|
224
|
+
supertrend: supertrend?.direction,
|
|
225
|
+
stochRsi: stochRsi?.k,
|
|
226
|
+
funding: fundingZ !== null
|
|
227
|
+
? `${fundingZ > 0 ? '+' : ''}${fundingZ.toFixed(1)}σ`
|
|
228
|
+
: 'unavailable',
|
|
229
|
+
oi: oi !== null ? `${oi.toFixed(2)}x avg` : 'unavailable',
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function computeEntry(strategy, fact, atr, currentPrice) {
|
|
233
|
+
const ind = fact.indicators;
|
|
234
|
+
const { type, params } = strategy.entryRule;
|
|
235
|
+
switch (type) {
|
|
236
|
+
case 'ema_offset': {
|
|
237
|
+
const emaKey = `ema${params.period ?? 20}`;
|
|
238
|
+
const emaRaw = ind[emaKey];
|
|
239
|
+
const ema = typeof emaRaw === 'number' && Number.isFinite(emaRaw) ? emaRaw : currentPrice;
|
|
240
|
+
const offset = atr * (params.atrFraction ?? 0.3);
|
|
241
|
+
return {
|
|
242
|
+
price: roundPrice(ema),
|
|
243
|
+
type: 'ema_offset',
|
|
244
|
+
rationale: `EMA${params.period ?? 20} pullback (±${offset.toFixed(0)} ATR offset)`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
case 'atr_offset': {
|
|
248
|
+
const fraction = params.atrFraction ?? 0.2;
|
|
249
|
+
return {
|
|
250
|
+
price: roundPrice(currentPrice),
|
|
251
|
+
type: 'atr_offset',
|
|
252
|
+
rationale: `Current price ±${(fraction * atr).toFixed(0)} ATR offset`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
case 'sweep_range':
|
|
256
|
+
default:
|
|
257
|
+
return {
|
|
258
|
+
price: roundPrice(currentPrice),
|
|
259
|
+
type: type || 'market',
|
|
260
|
+
rationale: 'At current price',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function computeStop(strategy, atr, entryPrice, direction) {
|
|
265
|
+
const { type, params } = strategy.stopRule;
|
|
266
|
+
// Direction sign: long → stop below entry (entry - ATR*mult), short → above.
|
|
267
|
+
const sign = direction === 'long' ? -1 : 1;
|
|
268
|
+
switch (type) {
|
|
269
|
+
case 'swing_atr': {
|
|
270
|
+
const mult = params.atrFraction ?? 0.5;
|
|
271
|
+
return {
|
|
272
|
+
price: roundPrice(entryPrice + sign * atr * (1 + mult)),
|
|
273
|
+
type: 'swing_atr',
|
|
274
|
+
rationale: `${direction === 'long' ? 'Below' : 'Above'} swing - ${mult} ATR buffer`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
case 'fixed_atr': {
|
|
278
|
+
const mult = params.atrMultiple ?? 1.5;
|
|
279
|
+
return {
|
|
280
|
+
price: roundPrice(entryPrice + sign * atr * mult),
|
|
281
|
+
type: 'fixed_atr',
|
|
282
|
+
rationale: `${mult}x ATR ${direction === 'long' ? 'below' : 'above'} entry`,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
case 'recent_extreme_atr': {
|
|
286
|
+
const mult = params.atrFraction ?? 0.3;
|
|
287
|
+
return {
|
|
288
|
+
price: roundPrice(entryPrice + sign * atr * (1 + mult)),
|
|
289
|
+
type: 'recent_extreme_atr',
|
|
290
|
+
rationale: `Recent extreme - ${mult} ATR`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
default:
|
|
294
|
+
return {
|
|
295
|
+
price: roundPrice(entryPrice + sign * atr * 1.5),
|
|
296
|
+
type: type || 'default',
|
|
297
|
+
rationale: `1.5x ATR default stop (${direction === 'long' ? 'below' : 'above'} entry)`,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function roundPrice(p) {
|
|
302
|
+
return Math.round(p * 100) / 100;
|
|
303
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IntelApiDeps } from './intel-api.js';
|
|
2
|
+
import type { LocalSignalService } from '../live/local-signal-service.js';
|
|
3
|
+
export interface GetSignalsDeps extends IntelApiDeps {
|
|
4
|
+
evaluatorMode?: 'central' | 'shadow' | 'enforce';
|
|
5
|
+
localSignalService?: LocalSignalService | null;
|
|
6
|
+
logger?: {
|
|
7
|
+
info: (tag: string, msg: string) => void;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function getSignalsTool(args: {
|
|
11
|
+
symbol: string;
|
|
12
|
+
hours?: number;
|
|
13
|
+
}, deps: GetSignalsDeps): Promise<Record<string, unknown> | {
|
|
14
|
+
error: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Tool: get_signals — active trading signals for a symbol.
|
|
2
|
+
//
|
|
3
|
+
// Default (`central`): fetches the snapshot + recent history from the intel
|
|
4
|
+
// engine, exactly as before. With facts-out (docs/FACTS_OUT_ARCHITECTURE.md
|
|
5
|
+
// step 5) the operator can flip `signals.evaluator`:
|
|
6
|
+
// shadow — return central, but ALSO log the locally-evaluated snapshot for
|
|
7
|
+
// comparison (zero behaviour change).
|
|
8
|
+
// enforce — return the locally-evaluated snapshot, falling back to central
|
|
9
|
+
// when the local service has no fact for the symbol yet.
|
|
10
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
11
|
+
async function central(args, deps) {
|
|
12
|
+
const s = enc(args.symbol);
|
|
13
|
+
const [snapshot, history] = await Promise.all([
|
|
14
|
+
fetchIntelApi(`/api/signals/${s}`, deps),
|
|
15
|
+
fetchIntelApi(`/api/signals/${s}/history?hours=${args.hours ?? 24}`, deps),
|
|
16
|
+
]);
|
|
17
|
+
if ('error' in snapshot)
|
|
18
|
+
return snapshot;
|
|
19
|
+
if ('error' in history)
|
|
20
|
+
return { ...snapshot, history: [] };
|
|
21
|
+
return { ...snapshot, history };
|
|
22
|
+
}
|
|
23
|
+
export async function getSignalsTool(args, deps) {
|
|
24
|
+
const mode = deps.evaluatorMode ?? 'central';
|
|
25
|
+
const svc = deps.localSignalService ?? null;
|
|
26
|
+
// ENFORCE — serve the local snapshot; fall back to central if no fact yet
|
|
27
|
+
// (or the streamed fact has gone stale — getSnapshot refuses those).
|
|
28
|
+
if (mode === 'enforce' && svc) {
|
|
29
|
+
const local = svc.getSnapshot(args.symbol);
|
|
30
|
+
if (local) {
|
|
31
|
+
return { ...local, history: local.recentSignals, source: 'local' };
|
|
32
|
+
}
|
|
33
|
+
// Fail open to central, but LOUDLY and tagged: during rollout the
|
|
34
|
+
// operator needs to see how often enforce actually serves local vs
|
|
35
|
+
// silently falling back (otherwise the capture rate is unmeasurable).
|
|
36
|
+
deps.logger?.info('signal-enforce-fallback', `${args.symbol} no fresh local fact — serving central`);
|
|
37
|
+
const fallback = await central(args, deps);
|
|
38
|
+
if (!('error' in fallback))
|
|
39
|
+
return { ...fallback, source: 'central_fallback' };
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
const result = await central(args, deps);
|
|
43
|
+
// SHADOW — central authoritative; log the local snapshot alongside for compare.
|
|
44
|
+
if (mode === 'shadow' && svc && !('error' in result)) {
|
|
45
|
+
const local = svc.getSnapshot(args.symbol);
|
|
46
|
+
const centralStrats = Array.isArray(result.activeStrategies)
|
|
47
|
+
? result.activeStrategies.length
|
|
48
|
+
: 0;
|
|
49
|
+
deps.logger?.info('signal-shadow', `${args.symbol} central_strategies=${centralStrats} ` +
|
|
50
|
+
`local_strategies=${local ? local.activeStrategies.length : 'no-fact'} ` +
|
|
51
|
+
`local_signals=${local ? local.recentSignals.length : 'n/a'}`);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|