@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,220 @@
|
|
|
1
|
+
// Tool: scan_pairs — scan all symbols for strategy setups.
|
|
2
|
+
// Fetches pre-computed facts from intelligence, evaluates user's active strategies,
|
|
3
|
+
// ranks by confluence score, returns concise summary (~300 tokens).
|
|
4
|
+
//
|
|
5
|
+
// Caching: facts and strategies share a process-wide cache with get_setup_detail
|
|
6
|
+
// (see intel-cache.ts). The shared cache eliminates the 2026-05-15 scan vs
|
|
7
|
+
// drill-down divergence — after a heartbeat scan, every get_setup_detail call
|
|
8
|
+
// for a symbol in the scan set sees the same snapshot the scan ranked it from.
|
|
9
|
+
//
|
|
10
|
+
// Learning pre-filter (2026-05-15): when decisionsDeps are wired, the scan
|
|
11
|
+
// fetches the user's confirmed entry-time learnings and splits results into
|
|
12
|
+
// `rankings` (no matching learning) and `vetoed_setups` (matching learning
|
|
13
|
+
// surfaced inline with the directive). The agent's eye lands on actionable
|
|
14
|
+
// candidates first; vetoed candidates are still visible so the agent can
|
|
15
|
+
// apply override conditions if the directive allows. Saves the drill-in
|
|
16
|
+
// round-trip on the ~6 macd_oversold_reversal-style candidates per
|
|
17
|
+
// heartbeat the agent was rejecting post-scorecard pre-fix.
|
|
18
|
+
import { scanAllPairs } from '../strategy/evaluator.js';
|
|
19
|
+
import { getAllFactsCached, getStrategiesCached, __testing__ as cacheTesting } from './intel-cache.js';
|
|
20
|
+
import { normalizeSetupFamily, resolveTriggerFamilies } from '../learning/setup-family.js';
|
|
21
|
+
const LEARNINGS_TTL_MS = 60_000;
|
|
22
|
+
const entryLearningsCache = new Map();
|
|
23
|
+
/** Family-keying gate (2026-06-22). Off by default → byte-identical exact-match
|
|
24
|
+
* behaviour. On → scan veto matches by stable setup_family, surviving the agent's
|
|
25
|
+
* free-text setup_type drift. See docs/CLAUDE/learning-loop.md. */
|
|
26
|
+
function familyKeyingEnabled() {
|
|
27
|
+
return process.env.RC_LEARNING_FAMILY_KEYING === 'on';
|
|
28
|
+
}
|
|
29
|
+
async function getEntryLearningsCached(client, userId) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const cached = entryLearningsCache.get(userId);
|
|
32
|
+
if (cached && now < cached.expiry)
|
|
33
|
+
return cached.value;
|
|
34
|
+
// No setup_type / regime filter — we want ALL the user's entry-time
|
|
35
|
+
// learnings so we can match them against every candidate locally.
|
|
36
|
+
let resp;
|
|
37
|
+
try {
|
|
38
|
+
resp = await client.getRelevantLearnings(userId, { appliesAt: 'entry' });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const value = resp?.learnings ?? [];
|
|
44
|
+
entryLearningsCache.set(userId, { value, expiry: now + LEARNINGS_TTL_MS });
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
/** True iff the learning's triggerCondition applies to the (setup, regime)
|
|
48
|
+
* pair, with rules tightened for scan-time pre-filter use.
|
|
49
|
+
*
|
|
50
|
+
* Two key differences vs the looser webapp matcher in
|
|
51
|
+
* /api/internal/learnings:
|
|
52
|
+
*
|
|
53
|
+
* 1. CAMEL/SNAKE TOLERANCE. The pattern miner emits `setupType` (camelCase)
|
|
54
|
+
* into mined_patterns.match_conditions, which is copied verbatim into
|
|
55
|
+
* learnings.trigger_condition on operator confirm. The webapp matcher
|
|
56
|
+
* reads `setup_type` only, so every confirmed learning silently falls
|
|
57
|
+
* through to the "missing key = wildcard" branch and matches every
|
|
58
|
+
* query. That broke scan_pairs' vetoed_setups bucket on the first live
|
|
59
|
+
* soak after the 2026-05-15 deploy — every candidate got pushed into
|
|
60
|
+
* vetoed because every learning was matching every candidate. We
|
|
61
|
+
* accept both forms here so the transition + write-side fix can land
|
|
62
|
+
* without bricking the matcher in between.
|
|
63
|
+
*
|
|
64
|
+
* 2. NO WILDCARD MATCH AT SCAN TIME. A learning with empty / missing
|
|
65
|
+
* trigger keys is intended as "applies broadly — remember this rule"
|
|
66
|
+
* at decision time (entry/heartbeat/close), NOT as "veto every scan
|
|
67
|
+
* candidate". For scan_pairs specifically, we require the trigger to
|
|
68
|
+
* pin AT LEAST ONE of {setup, regime} explicitly. Otherwise scan
|
|
69
|
+
* produces zero rankings and the agent has to bypass the scanner
|
|
70
|
+
* entirely — which is what the 2026-05-15 16:53 UTC heartbeat reported.
|
|
71
|
+
*/
|
|
72
|
+
function learningMatches(learning, setupType, regime) {
|
|
73
|
+
const t = learning.triggerCondition;
|
|
74
|
+
if (!t || typeof t !== 'object')
|
|
75
|
+
return false; // no constraints → not a scan veto
|
|
76
|
+
const tObj = t;
|
|
77
|
+
// Accept either snake_case or camelCase from the JSONB. Snake_case is the
|
|
78
|
+
// canonical form (matches plugin tool args, SKILL.md, validator); camelCase
|
|
79
|
+
// is the transitional form the pattern miner emitted pre-2026-05-15.
|
|
80
|
+
const tSetup = tObj.setup_type ?? tObj.setupType;
|
|
81
|
+
const tRegime = tObj.regime ?? tObj.regimeKey;
|
|
82
|
+
const tFamily = tObj.setup_family ?? tObj.setupFamily;
|
|
83
|
+
if (familyKeyingEnabled()) {
|
|
84
|
+
// Family-aware scan veto (2026-06-22): match by stable setup_family so a
|
|
85
|
+
// confirmed "avoid X" learning keeps vetoing the family as the agent renames
|
|
86
|
+
// its setups. resolveTriggerFamilies normalizes setup_type → family, or uses
|
|
87
|
+
// an explicit setup_family from the family-keyed miner.
|
|
88
|
+
const targetFamilies = resolveTriggerFamilies(tFamily, tSetup);
|
|
89
|
+
// Still require at least one constraint (setup family OR regime) — an empty
|
|
90
|
+
// trigger must NOT veto every candidate (the 2026-05-15 wildcard bug).
|
|
91
|
+
if (targetFamilies == null && tRegime == null)
|
|
92
|
+
return false;
|
|
93
|
+
if (targetFamilies != null &&
|
|
94
|
+
!targetFamilies.includes(normalizeSetupFamily(setupType))) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (tRegime != null) {
|
|
98
|
+
if (Array.isArray(tRegime)) {
|
|
99
|
+
if (!tRegime.includes(regime))
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
else if (tRegime !== regime)
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
// Legacy exact-match veto (default).
|
|
108
|
+
// Require at least one constraint to count as a scan-time veto.
|
|
109
|
+
if (tSetup == null && tRegime == null)
|
|
110
|
+
return false;
|
|
111
|
+
if (tSetup != null) {
|
|
112
|
+
if (Array.isArray(tSetup)) {
|
|
113
|
+
if (!tSetup.includes(setupType))
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
else if (tSetup !== setupType)
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (tRegime != null) {
|
|
120
|
+
if (Array.isArray(tRegime)) {
|
|
121
|
+
if (!tRegime.includes(regime))
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
else if (tRegime !== regime)
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
export async function scanPairsTool(args, deps, decisionsDeps) {
|
|
130
|
+
const minScore = args.min_score ?? 4;
|
|
131
|
+
// 1. Fetch all symbol facts (cached, shared with get_setup_detail)
|
|
132
|
+
const factsRes = await getAllFactsCached(deps);
|
|
133
|
+
if ('error' in factsRes)
|
|
134
|
+
return factsRes;
|
|
135
|
+
let facts = factsRes;
|
|
136
|
+
// Optional symbols filter — narrows the scan to a watchlist.
|
|
137
|
+
if (Array.isArray(args.symbols) && args.symbols.length > 0) {
|
|
138
|
+
const wl = new Set(args.symbols.map(s => s.toUpperCase()));
|
|
139
|
+
facts = facts.filter(f => wl.has(f.symbol.toUpperCase()));
|
|
140
|
+
}
|
|
141
|
+
if (facts.length === 0) {
|
|
142
|
+
return {
|
|
143
|
+
error: args.symbols && args.symbols.length > 0
|
|
144
|
+
? `No facts available for the requested symbols (${args.symbols.join(', ')}). Intelligence may still be computing initial data, or the symbols are unsupported.`
|
|
145
|
+
: 'No symbol facts available. Intelligence service may still be computing initial data.',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// 2. Fetch user's active strategies (cached, shared with get_setup_detail)
|
|
149
|
+
const stratRes = await getStrategiesCached(deps);
|
|
150
|
+
if ('error' in stratRes)
|
|
151
|
+
return stratRes;
|
|
152
|
+
const strategies = stratRes;
|
|
153
|
+
if (strategies.length === 0) {
|
|
154
|
+
return {
|
|
155
|
+
timestamp: new Date().toISOString(),
|
|
156
|
+
pairs_scanned: facts.length,
|
|
157
|
+
setups_found: 0,
|
|
158
|
+
note: 'No active strategies. Create or activate strategies using save_strategy or toggle_strategy.',
|
|
159
|
+
rankings: [],
|
|
160
|
+
no_setup: facts.map(f => f.symbol),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// 3. Evaluate all strategies against all facts
|
|
164
|
+
const results = scanAllPairs(strategies, facts, minScore);
|
|
165
|
+
// 4. Fetch user's confirmed entry-time learnings (optional path — fail
|
|
166
|
+
// quietly when the decisions client isn't wired, e.g. in dev or in
|
|
167
|
+
// tests that exercise the pre-learning behaviour).
|
|
168
|
+
let entryLearnings = [];
|
|
169
|
+
if (decisionsDeps?.decisionsClient && decisionsDeps.userId) {
|
|
170
|
+
entryLearnings = await getEntryLearningsCached(decisionsDeps.decisionsClient, decisionsDeps.userId);
|
|
171
|
+
}
|
|
172
|
+
const rankings = [];
|
|
173
|
+
const vetoed = [];
|
|
174
|
+
for (const r of results) {
|
|
175
|
+
const matches = entryLearnings.filter(l => learningMatches(l, r.strategy, r.regime));
|
|
176
|
+
const out = {
|
|
177
|
+
symbol: r.symbol,
|
|
178
|
+
score: r.score,
|
|
179
|
+
regime: r.regime,
|
|
180
|
+
strategy: r.strategy,
|
|
181
|
+
conditions: `${r.conditionsMet}/${r.conditionsTotal} met: ${r.conditions.filter(c => c.met).map(c => c.name).join(', ')}`,
|
|
182
|
+
summary: r.summary,
|
|
183
|
+
};
|
|
184
|
+
if (matches.length === 0) {
|
|
185
|
+
rankings.push(out);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
vetoed.push({
|
|
189
|
+
...out,
|
|
190
|
+
matched_learnings: matches.map(l => ({
|
|
191
|
+
id: l.id, title: l.title, directive: l.directive,
|
|
192
|
+
})),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (rankings.length >= 10)
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
// 6. Build concise response
|
|
199
|
+
const setupSymbols = new Set(results.map(r => r.symbol));
|
|
200
|
+
const noSetup = facts
|
|
201
|
+
.map(f => f.symbol)
|
|
202
|
+
.filter(s => !setupSymbols.has(s));
|
|
203
|
+
return {
|
|
204
|
+
timestamp: new Date().toISOString(),
|
|
205
|
+
pairs_scanned: facts.length,
|
|
206
|
+
setups_found: results.length,
|
|
207
|
+
rankings,
|
|
208
|
+
vetoed_setups: vetoed.length > 0 ? vetoed : undefined,
|
|
209
|
+
no_setup: noSetup.length > 5
|
|
210
|
+
? [...noSetup.slice(0, 5), `...${noSetup.length - 5} others`]
|
|
211
|
+
: noSetup,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Test-only — clears the shared in-memory TTL caches between unit tests.
|
|
215
|
+
export const __testing__ = {
|
|
216
|
+
resetCaches() {
|
|
217
|
+
cacheTesting.resetCaches();
|
|
218
|
+
entryLearningsCache.clear();
|
|
219
|
+
},
|
|
220
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { IntelApiDeps } from './intel-api.js';
|
|
2
|
+
interface ScoreSetupArgs {
|
|
3
|
+
symbol: string;
|
|
4
|
+
direction: 'LONG' | 'SHORT';
|
|
5
|
+
setup_type: string;
|
|
6
|
+
entry_price: number;
|
|
7
|
+
stop_price: number;
|
|
8
|
+
target_price: number;
|
|
9
|
+
}
|
|
10
|
+
interface DimensionScore {
|
|
11
|
+
score: number;
|
|
12
|
+
detail: string;
|
|
13
|
+
pass: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface ScorecardResult {
|
|
16
|
+
composite: number;
|
|
17
|
+
verdict: 'STRONG_GO' | 'GO' | 'MARGINAL' | 'NO_GO';
|
|
18
|
+
dimensions: {
|
|
19
|
+
riskReward: DimensionScore;
|
|
20
|
+
regimeAlignment: DimensionScore;
|
|
21
|
+
regimeConfidence: DimensionScore;
|
|
22
|
+
setupEdge: DimensionScore;
|
|
23
|
+
flowAlignment: DimensionScore;
|
|
24
|
+
};
|
|
25
|
+
flags: string[];
|
|
26
|
+
recommendation: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function scoreSetupTool(args: ScoreSetupArgs, deps: IntelApiDeps): Promise<ScorecardResult | {
|
|
29
|
+
error: string;
|
|
30
|
+
}>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Tool: score_setup — Pre-trade setup scorecard.
|
|
2
|
+
// Consolidates regime, feedback, and flow data into a single scored verdict.
|
|
3
|
+
// Forces the agent to only trade high-quality setups.
|
|
4
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
5
|
+
import { getTradingParams } from '../trading-params-cache.js';
|
|
6
|
+
export async function scoreSetupTool(args, deps) {
|
|
7
|
+
const { symbol, direction, setup_type, entry_price, stop_price, target_price } = args;
|
|
8
|
+
// Validate inputs
|
|
9
|
+
if (direction === 'LONG' && stop_price >= entry_price) {
|
|
10
|
+
return { error: 'For LONG: stop_price must be below entry_price' };
|
|
11
|
+
}
|
|
12
|
+
if (direction === 'SHORT' && stop_price <= entry_price) {
|
|
13
|
+
return { error: 'For SHORT: stop_price must be above entry_price' };
|
|
14
|
+
}
|
|
15
|
+
// Fetch all intelligence data + trading params in parallel
|
|
16
|
+
const s = enc(symbol);
|
|
17
|
+
const [regimeData, feedbackData, flowData, tp] = await Promise.all([
|
|
18
|
+
fetchIntelApi(`/api/regime/${s}`, deps),
|
|
19
|
+
fetchIntelApi(`/api/analytics/${s}/feedback`, deps),
|
|
20
|
+
fetchIntelApi(`/api/trade-flow/${s}?minutes=5`, deps),
|
|
21
|
+
getTradingParams(deps),
|
|
22
|
+
]);
|
|
23
|
+
const flags = [];
|
|
24
|
+
// ── Dimension 1: Risk/Reward ──
|
|
25
|
+
const risk = Math.abs(entry_price - stop_price);
|
|
26
|
+
const reward = Math.abs(target_price - entry_price);
|
|
27
|
+
const rr = risk > 0 ? reward / risk : 0;
|
|
28
|
+
let rrScore;
|
|
29
|
+
if (rr >= 3)
|
|
30
|
+
rrScore = 10;
|
|
31
|
+
else if (rr >= 2.5)
|
|
32
|
+
rrScore = 9;
|
|
33
|
+
else if (rr >= 2)
|
|
34
|
+
rrScore = 8;
|
|
35
|
+
else if (rr >= 1.5)
|
|
36
|
+
rrScore = 6;
|
|
37
|
+
else if (rr >= 1)
|
|
38
|
+
rrScore = 4;
|
|
39
|
+
else
|
|
40
|
+
rrScore = 2;
|
|
41
|
+
if (rr < tp.rrWarningThreshold)
|
|
42
|
+
flags.push(`Poor R:R (${rr.toFixed(2)}:1) — minimum ${tp.rrWarningThreshold}:1 recommended`);
|
|
43
|
+
const riskReward = {
|
|
44
|
+
score: rrScore,
|
|
45
|
+
detail: `R:R ${rr.toFixed(2)}:1 (risk $${risk.toFixed(2)}, reward $${reward.toFixed(2)})`,
|
|
46
|
+
pass: rrScore >= 5,
|
|
47
|
+
};
|
|
48
|
+
// ── Dimension 2: Regime Alignment ──
|
|
49
|
+
let regimeScore = 5; // neutral default
|
|
50
|
+
let regimeDetail = 'Regime data unavailable';
|
|
51
|
+
if (!('error' in regimeData)) {
|
|
52
|
+
const regime = String(regimeData.regime ?? '').toUpperCase();
|
|
53
|
+
const confidence = Number(regimeData.confidence ?? 0);
|
|
54
|
+
const bullishRegimes = ['TREND_UP', 'VOLATILITY_EXPANSION'];
|
|
55
|
+
const bearishRegimes = ['TREND_DOWN'];
|
|
56
|
+
const neutralRegimes = ['RANGE_TIGHT', 'RANGE_WIDE'];
|
|
57
|
+
const avoidRegimes = ['AVOID', 'UNKNOWN'];
|
|
58
|
+
if (avoidRegimes.includes(regime)) {
|
|
59
|
+
regimeScore = 1;
|
|
60
|
+
regimeDetail = `Regime ${regime} — NO trading recommended`;
|
|
61
|
+
flags.push(`Regime is ${regime} — do not trade`);
|
|
62
|
+
}
|
|
63
|
+
else if (direction === 'LONG' && bullishRegimes.includes(regime)) {
|
|
64
|
+
regimeScore = 9;
|
|
65
|
+
regimeDetail = `${regime} aligns with LONG direction`;
|
|
66
|
+
}
|
|
67
|
+
else if (direction === 'SHORT' && bearishRegimes.includes(regime)) {
|
|
68
|
+
regimeScore = 9;
|
|
69
|
+
regimeDetail = `${regime} aligns with SHORT direction`;
|
|
70
|
+
}
|
|
71
|
+
else if (direction === 'LONG' && bearishRegimes.includes(regime)) {
|
|
72
|
+
regimeScore = 2;
|
|
73
|
+
regimeDetail = `${regime} opposes LONG direction`;
|
|
74
|
+
flags.push(`Trading LONG against ${regime} regime`);
|
|
75
|
+
}
|
|
76
|
+
else if (direction === 'SHORT' && bullishRegimes.includes(regime)) {
|
|
77
|
+
regimeScore = 2;
|
|
78
|
+
regimeDetail = `${regime} opposes SHORT direction`;
|
|
79
|
+
flags.push(`Trading SHORT against ${regime} regime`);
|
|
80
|
+
}
|
|
81
|
+
else if (neutralRegimes.includes(regime)) {
|
|
82
|
+
regimeScore = 5;
|
|
83
|
+
regimeDetail = `${regime} — neutral for ${direction}. Mean reversion setups preferred.`;
|
|
84
|
+
}
|
|
85
|
+
// Adjust for confidence — only penalize below user-configured threshold
|
|
86
|
+
if (confidence < tp.confidencePenaltyThreshold && !avoidRegimes.includes(regime)) {
|
|
87
|
+
regimeScore = Math.max(3, regimeScore - 2);
|
|
88
|
+
flags.push(`Low regime confidence (${(confidence * 100).toFixed(0)}%) — treat with caution`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const regimeAlignment = {
|
|
92
|
+
score: regimeScore,
|
|
93
|
+
detail: regimeDetail,
|
|
94
|
+
pass: regimeScore >= 5,
|
|
95
|
+
};
|
|
96
|
+
// ── Dimension 3: Regime Confidence ──
|
|
97
|
+
let confScore = 5;
|
|
98
|
+
let confDetail = 'Regime confidence unavailable';
|
|
99
|
+
if (!('error' in regimeData)) {
|
|
100
|
+
const confidence = Number(regimeData.confidence ?? 0);
|
|
101
|
+
confScore = Math.round(confidence * 10);
|
|
102
|
+
confDetail = `Regime confidence: ${(confidence * 100).toFixed(0)}%`;
|
|
103
|
+
if (confidence < tp.confidencePenaltyThreshold) {
|
|
104
|
+
flags.push(`Regime classifier uncertain (${(confidence * 100).toFixed(0)}%)`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const regimeConfidence = {
|
|
108
|
+
score: confScore,
|
|
109
|
+
detail: confDetail,
|
|
110
|
+
pass: confScore >= 5,
|
|
111
|
+
};
|
|
112
|
+
// ── Dimension 4: Setup Edge (from feedback) ──
|
|
113
|
+
let edgeScore = 5;
|
|
114
|
+
let edgeDetail = 'No feedback data for this setup type';
|
|
115
|
+
if (!('error' in feedbackData)) {
|
|
116
|
+
const fb = feedbackData;
|
|
117
|
+
const bySetup = fb.bySetupType;
|
|
118
|
+
if (bySetup) {
|
|
119
|
+
const match = bySetup.find((s) => String(s.setupType).toLowerCase() === setup_type.toLowerCase());
|
|
120
|
+
if (match) {
|
|
121
|
+
const verdict = String(match.verdict ?? '');
|
|
122
|
+
const edgeTrend = String(match.edgeTrend ?? 'insufficient_data');
|
|
123
|
+
const winRate = Number(match.winRate ?? 0);
|
|
124
|
+
const winRate20 = match.winRate20 != null ? Number(match.winRate20) : null;
|
|
125
|
+
const trades = Number(match.trades ?? 0);
|
|
126
|
+
if (verdict === 'STRONG')
|
|
127
|
+
edgeScore = 9;
|
|
128
|
+
else if (verdict === 'WORKING')
|
|
129
|
+
edgeScore = 7;
|
|
130
|
+
else if (verdict === 'MARGINAL')
|
|
131
|
+
edgeScore = 4;
|
|
132
|
+
else if (verdict === 'FAILING') {
|
|
133
|
+
edgeScore = 2;
|
|
134
|
+
flags.push(`Setup "${setup_type}" is FAILING — use half size or skip`);
|
|
135
|
+
}
|
|
136
|
+
// Adjust for edge trend
|
|
137
|
+
if (edgeTrend === 'declining') {
|
|
138
|
+
edgeScore = Math.max(1, edgeScore - 2);
|
|
139
|
+
flags.push(`Setup "${setup_type}" edge is declining — reduce size`);
|
|
140
|
+
}
|
|
141
|
+
else if (edgeTrend === 'improving') {
|
|
142
|
+
edgeScore = Math.min(10, edgeScore + 1);
|
|
143
|
+
}
|
|
144
|
+
const wrStr = winRate20 != null
|
|
145
|
+
? `${(winRate20 * 100).toFixed(0)}% (last 20) / ${(winRate * 100).toFixed(0)}% (all)`
|
|
146
|
+
: `${(winRate * 100).toFixed(0)}%`;
|
|
147
|
+
edgeDetail = `${setup_type}: ${verdict} (${wrStr}, ${trades} trades, trend: ${edgeTrend})`;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
edgeScore = tp.unprovenSetupScore;
|
|
151
|
+
edgeDetail = `No history for "${setup_type}" — new setup, proceed with reduced size`;
|
|
152
|
+
flags.push(`First time using setup "${setup_type}" — reduce size`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const setupEdge = {
|
|
157
|
+
score: edgeScore,
|
|
158
|
+
detail: edgeDetail,
|
|
159
|
+
pass: edgeScore >= 5,
|
|
160
|
+
};
|
|
161
|
+
// ── Dimension 5: Institutional Flow Alignment ──
|
|
162
|
+
let flowScore = 5;
|
|
163
|
+
let flowDetail = 'Flow data unavailable';
|
|
164
|
+
if (!('error' in flowData)) {
|
|
165
|
+
const flow = flowData;
|
|
166
|
+
const pressure = String(flow.whalePressure ?? 'neutral').toLowerCase();
|
|
167
|
+
const ratio = Number(flow.buySellRatio ?? 1);
|
|
168
|
+
const largeRatio = Number(flow.largeTradeRatio ?? 0);
|
|
169
|
+
const bullishFlow = pressure === 'strong_buy' || pressure === 'buy';
|
|
170
|
+
const bearishFlow = pressure === 'strong_sell' || pressure === 'sell';
|
|
171
|
+
if (direction === 'LONG') {
|
|
172
|
+
if (pressure === 'strong_buy')
|
|
173
|
+
flowScore = 10;
|
|
174
|
+
else if (pressure === 'buy')
|
|
175
|
+
flowScore = 8;
|
|
176
|
+
else if (pressure === 'neutral')
|
|
177
|
+
flowScore = 5;
|
|
178
|
+
else if (pressure === 'sell')
|
|
179
|
+
flowScore = 3;
|
|
180
|
+
else if (pressure === 'strong_sell') {
|
|
181
|
+
flowScore = 1;
|
|
182
|
+
flags.push('Institutional flow strongly AGAINST your LONG direction');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
if (pressure === 'strong_sell')
|
|
187
|
+
flowScore = 10;
|
|
188
|
+
else if (pressure === 'sell')
|
|
189
|
+
flowScore = 8;
|
|
190
|
+
else if (pressure === 'neutral')
|
|
191
|
+
flowScore = 5;
|
|
192
|
+
else if (pressure === 'buy')
|
|
193
|
+
flowScore = 3;
|
|
194
|
+
else if (pressure === 'strong_buy') {
|
|
195
|
+
flowScore = 1;
|
|
196
|
+
flags.push('Institutional flow strongly AGAINST your SHORT direction');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Bonus for high institutional activity
|
|
200
|
+
if (largeRatio > 0.15 && ((direction === 'LONG' && bullishFlow) || (direction === 'SHORT' && bearishFlow))) {
|
|
201
|
+
flowScore = Math.min(10, flowScore + 1);
|
|
202
|
+
}
|
|
203
|
+
flowDetail = `Whale pressure: ${pressure}, buy/sell ratio: ${ratio.toFixed(2)}, institutional volume: ${(largeRatio * 100).toFixed(1)}%`;
|
|
204
|
+
}
|
|
205
|
+
const flowAlignment = {
|
|
206
|
+
score: flowScore,
|
|
207
|
+
detail: flowDetail,
|
|
208
|
+
pass: flowScore >= 5,
|
|
209
|
+
};
|
|
210
|
+
// ── Composite Score (weighted average) ──
|
|
211
|
+
const weights = {
|
|
212
|
+
riskReward: 2, // R:R is fundamental
|
|
213
|
+
regimeAlignment: 3, // regime is the most important filter
|
|
214
|
+
regimeConfidence: 1, // confidence matters but less than alignment
|
|
215
|
+
setupEdge: 2.5, // your track record with this setup
|
|
216
|
+
flowAlignment: 1.5, // institutional confirmation
|
|
217
|
+
};
|
|
218
|
+
const totalWeight = weights.riskReward + weights.regimeAlignment + weights.regimeConfidence + weights.setupEdge + weights.flowAlignment;
|
|
219
|
+
const composite = (riskReward.score * weights.riskReward +
|
|
220
|
+
regimeAlignment.score * weights.regimeAlignment +
|
|
221
|
+
regimeConfidence.score * weights.regimeConfidence +
|
|
222
|
+
setupEdge.score * weights.setupEdge +
|
|
223
|
+
flowAlignment.score * weights.flowAlignment) / totalWeight;
|
|
224
|
+
// ── Verdict ──
|
|
225
|
+
let verdict;
|
|
226
|
+
if (composite >= tp.scorecardStrongGoThreshold)
|
|
227
|
+
verdict = 'STRONG_GO';
|
|
228
|
+
else if (composite >= tp.scorecardGoThreshold)
|
|
229
|
+
verdict = 'GO';
|
|
230
|
+
else if (composite >= tp.scorecardMarginalThreshold)
|
|
231
|
+
verdict = 'MARGINAL';
|
|
232
|
+
else
|
|
233
|
+
verdict = 'NO_GO';
|
|
234
|
+
// Hard vetoes — any critical failure overrides composite
|
|
235
|
+
if (regimeScore <= 2)
|
|
236
|
+
verdict = 'NO_GO';
|
|
237
|
+
if (edgeScore <= 1)
|
|
238
|
+
verdict = 'NO_GO';
|
|
239
|
+
// ── Recommendation ──
|
|
240
|
+
let recommendation;
|
|
241
|
+
switch (verdict) {
|
|
242
|
+
case 'STRONG_GO':
|
|
243
|
+
recommendation = `High-quality setup. ${setup_type} ${direction} with ${rr.toFixed(1)}:1 R:R — all dimensions aligned.`;
|
|
244
|
+
break;
|
|
245
|
+
case 'GO':
|
|
246
|
+
recommendation = `Acceptable setup. Proceed with standard sizing. Watch: ${flags.length > 0 ? flags[0] : 'no concerns'}.`;
|
|
247
|
+
break;
|
|
248
|
+
case 'MARGINAL':
|
|
249
|
+
recommendation = `Borderline setup. If you trade this, use half size. Issues: ${flags.join('; ') || 'multiple dimensions below threshold'}.`;
|
|
250
|
+
break;
|
|
251
|
+
case 'NO_GO':
|
|
252
|
+
recommendation = `Do not trade. ${flags.join('; ') || 'Multiple critical failures'}.`;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
composite: +composite.toFixed(1),
|
|
257
|
+
verdict,
|
|
258
|
+
dimensions: {
|
|
259
|
+
riskReward,
|
|
260
|
+
regimeAlignment,
|
|
261
|
+
regimeConfidence,
|
|
262
|
+
setupEdge,
|
|
263
|
+
flowAlignment,
|
|
264
|
+
},
|
|
265
|
+
flags,
|
|
266
|
+
recommendation,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type BracketRequirementFlag = 'requireStopLoss' | 'requireTakeProfit';
|
|
2
|
+
export interface SetBracketRequirementArgs {
|
|
3
|
+
flag: BracketRequirementFlag;
|
|
4
|
+
value: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface SetBracketRequirementResult {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
flag: BracketRequirementFlag;
|
|
9
|
+
previousValue: boolean;
|
|
10
|
+
value: boolean;
|
|
11
|
+
warning?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SetBracketRequirementDeps {
|
|
15
|
+
/** Override the config file path — tests use this. */
|
|
16
|
+
configPath?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function setBracketRequirementTool(args: SetBracketRequirementArgs, deps?: SetBracketRequirementDeps): SetBracketRequirementResult;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// set_bracket_requirement — operator-only plugin tool.
|
|
2
|
+
//
|
|
3
|
+
// Flips requireStopLoss or requireTakeProfit in plugin-config.json. The
|
|
4
|
+
// dashboard's Trading Parameters panel calls this when the operator toggles
|
|
5
|
+
// one of the TP/SL checkboxes.
|
|
6
|
+
//
|
|
7
|
+
// Design decisions:
|
|
8
|
+
// - No adapter swap needed — the pre-trade gate reads flags via
|
|
9
|
+
// loadBracketRequirements() on every create_order call, so changes take
|
|
10
|
+
// effect on the very next order.
|
|
11
|
+
// - Disabling either flag returns `warning: true` + a human message so the
|
|
12
|
+
// UI can render the confirmation banner ("you are accepting unprotected
|
|
13
|
+
// trades").
|
|
14
|
+
// - Default refusal: the agent must NOT call this — the skill layer is
|
|
15
|
+
// expected to guard with operator-only scope. This handler stays
|
|
16
|
+
// functional but unguarded for parity with set_trading_mode.
|
|
17
|
+
import { readPluginConfig, updatePluginConfig } from '../config/plugin-config-io.js';
|
|
18
|
+
import { getBracketRequirements } from '../config/brackets-config.js';
|
|
19
|
+
import { formatError } from '../logger.js';
|
|
20
|
+
export function setBracketRequirementTool(args, deps = {}) {
|
|
21
|
+
if (!args || (args.flag !== 'requireStopLoss' && args.flag !== 'requireTakeProfit')) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
flag: args?.flag ?? 'requireStopLoss',
|
|
25
|
+
previousValue: true,
|
|
26
|
+
value: true,
|
|
27
|
+
error: `Invalid flag. Must be 'requireStopLoss' or 'requireTakeProfit'.`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (typeof args.value !== 'boolean') {
|
|
31
|
+
return {
|
|
32
|
+
ok: false,
|
|
33
|
+
flag: args.flag,
|
|
34
|
+
previousValue: true,
|
|
35
|
+
value: true,
|
|
36
|
+
error: `value must be a boolean.`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
let previousValue;
|
|
40
|
+
try {
|
|
41
|
+
const current = readPluginConfig(deps.configPath);
|
|
42
|
+
previousValue = getBracketRequirements(current)[args.flag];
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
flag: args.flag,
|
|
48
|
+
previousValue: true,
|
|
49
|
+
value: args.value,
|
|
50
|
+
error: `Cannot read config: ${formatError(err)}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
updatePluginConfig({ brackets: { [args.flag]: args.value } }, deps.configPath);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
flag: args.flag,
|
|
60
|
+
previousValue,
|
|
61
|
+
value: args.value,
|
|
62
|
+
error: `Cannot write config: ${formatError(err)}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const result = {
|
|
66
|
+
ok: true,
|
|
67
|
+
flag: args.flag,
|
|
68
|
+
previousValue,
|
|
69
|
+
value: args.value,
|
|
70
|
+
};
|
|
71
|
+
// Warning copy surfaces in the dashboard as a confirmation banner.
|
|
72
|
+
if (!args.value) {
|
|
73
|
+
if (args.flag === 'requireStopLoss') {
|
|
74
|
+
result.warning = 'Stop-loss requirement disabled. Live trades may be submitted without a stop — downside is unbounded on adverse moves. Re-enable as soon as you are done with the override.';
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
result.warning = 'Take-profit requirement disabled. Live trades may run without a target — operator must monitor exits manually.';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PluginRuntime } from '../onboarding/runtime.js';
|
|
2
|
+
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
3
|
+
import type { TradingMode } from '../types.js';
|
|
4
|
+
export interface SetExchangeCredentialsArgs {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
secret: string;
|
|
7
|
+
testnet?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface SetExchangeCredentialsResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
message: string;
|
|
12
|
+
fingerprint: string;
|
|
13
|
+
reconnected: boolean;
|
|
14
|
+
mode: TradingMode;
|
|
15
|
+
readiness: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SetExchangeCredentialsDeps {
|
|
18
|
+
runtime: PluginRuntime;
|
|
19
|
+
adapterDeps: {
|
|
20
|
+
adapter: IExchangeAdapter;
|
|
21
|
+
};
|
|
22
|
+
/** Override the config file path — tests use this. */
|
|
23
|
+
configPath?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function setExchangeCredentialsTool(args: SetExchangeCredentialsArgs, deps: SetExchangeCredentialsDeps): Promise<SetExchangeCredentialsResult>;
|