@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,198 @@
|
|
|
1
|
+
// Tool: get_market_structure — multi-timeframe technical analysis via CCXT
|
|
2
|
+
// Fetches OHLCV for multiple timeframes and computes trend, regime, momentum.
|
|
3
|
+
// Used by the agent (via tool call) and broadcast to the dashboard (via poller).
|
|
4
|
+
// --- Technical indicator helpers ---
|
|
5
|
+
function sma(values, period) {
|
|
6
|
+
if (values.length < period)
|
|
7
|
+
return null;
|
|
8
|
+
const slice = values.slice(-period);
|
|
9
|
+
return slice.reduce((s, v) => s + v, 0) / period;
|
|
10
|
+
}
|
|
11
|
+
function ema(values, period) {
|
|
12
|
+
if (values.length < period)
|
|
13
|
+
return null;
|
|
14
|
+
const k = 2 / (period + 1);
|
|
15
|
+
let e = values.slice(0, period).reduce((s, v) => s + v, 0) / period;
|
|
16
|
+
for (let i = period; i < values.length; i++) {
|
|
17
|
+
e = values[i] * k + e * (1 - k);
|
|
18
|
+
}
|
|
19
|
+
return e;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Average True Range using Wilder's smoothing.
|
|
23
|
+
* Input: OHLCV candles array, period (default 14).
|
|
24
|
+
* Returns null if insufficient data.
|
|
25
|
+
*/
|
|
26
|
+
export function atr(candles, period = 14) {
|
|
27
|
+
if (candles.length < period + 1)
|
|
28
|
+
return null;
|
|
29
|
+
// Compute true range for each bar (starting from index 1)
|
|
30
|
+
const trueRanges = [];
|
|
31
|
+
for (let i = 1; i < candles.length; i++) {
|
|
32
|
+
const high = candles[i][2];
|
|
33
|
+
const low = candles[i][3];
|
|
34
|
+
const prevClose = candles[i - 1][4];
|
|
35
|
+
const tr = Math.max(high - low, Math.abs(high - prevClose), Math.abs(low - prevClose));
|
|
36
|
+
trueRanges.push(tr);
|
|
37
|
+
}
|
|
38
|
+
if (trueRanges.length < period)
|
|
39
|
+
return null;
|
|
40
|
+
// Initial ATR: simple average of first `period` true ranges
|
|
41
|
+
let atrVal = trueRanges.slice(0, period).reduce((s, v) => s + v, 0) / period;
|
|
42
|
+
// Wilder's smoothing for remaining
|
|
43
|
+
for (let i = period; i < trueRanges.length; i++) {
|
|
44
|
+
atrVal = (atrVal * (period - 1) + trueRanges[i]) / period;
|
|
45
|
+
}
|
|
46
|
+
return atrVal;
|
|
47
|
+
}
|
|
48
|
+
function rsi(closes, period = 14) {
|
|
49
|
+
if (closes.length < period + 1)
|
|
50
|
+
return null;
|
|
51
|
+
const recent = closes.slice(-(period + 1));
|
|
52
|
+
let gains = 0, losses = 0;
|
|
53
|
+
for (let i = 1; i < recent.length; i++) {
|
|
54
|
+
const diff = recent[i] - recent[i - 1];
|
|
55
|
+
if (diff > 0)
|
|
56
|
+
gains += diff;
|
|
57
|
+
else
|
|
58
|
+
losses -= diff;
|
|
59
|
+
}
|
|
60
|
+
if (losses === 0)
|
|
61
|
+
return 100;
|
|
62
|
+
const rs = (gains / period) / (losses / period);
|
|
63
|
+
return 100 - (100 / (1 + rs));
|
|
64
|
+
}
|
|
65
|
+
// --- Timeframes to analyze ---
|
|
66
|
+
const TIMEFRAMES = [
|
|
67
|
+
{ id: '5m', ccxt: '5m', limit: 50 },
|
|
68
|
+
{ id: '15m', ccxt: '15m', limit: 50 },
|
|
69
|
+
{ id: '1h', ccxt: '1h', limit: 50 },
|
|
70
|
+
{ id: '4h', ccxt: '4h', limit: 50 },
|
|
71
|
+
{ id: '1d', ccxt: '1d', limit: 30 },
|
|
72
|
+
{ id: '1w', ccxt: '1w', limit: 20 },
|
|
73
|
+
];
|
|
74
|
+
// --- Analysis logic ---
|
|
75
|
+
function analyzeCandles(timeframe, candles) {
|
|
76
|
+
const closes = candles.map((c) => c[4]);
|
|
77
|
+
const atr14 = atr(candles, 14);
|
|
78
|
+
const rsiVal = rsi(closes, 14);
|
|
79
|
+
const sma20 = sma(closes, 20);
|
|
80
|
+
const emaFastVal = ema(closes, 9);
|
|
81
|
+
const emaSlowVal = ema(closes, 21);
|
|
82
|
+
const lastClose = closes.length > 0 ? closes[closes.length - 1] : null;
|
|
83
|
+
// Price vs SMA20 (%)
|
|
84
|
+
const priceVsSma = lastClose !== null && sma20 !== null && sma20 > 0
|
|
85
|
+
? ((lastClose - sma20) / sma20) * 100
|
|
86
|
+
: null;
|
|
87
|
+
// Change % over period
|
|
88
|
+
const changePercent = closes.length >= 2
|
|
89
|
+
? ((closes[closes.length - 1] - closes[0]) / closes[0]) * 100
|
|
90
|
+
: null;
|
|
91
|
+
// Direction: EMA crossover
|
|
92
|
+
let direction = 'neutral';
|
|
93
|
+
if (emaFastVal !== null && emaSlowVal !== null) {
|
|
94
|
+
const emaDiff = ((emaFastVal - emaSlowVal) / emaSlowVal) * 100;
|
|
95
|
+
if (emaDiff > 0.05)
|
|
96
|
+
direction = 'bullish';
|
|
97
|
+
else if (emaDiff < -0.05)
|
|
98
|
+
direction = 'bearish';
|
|
99
|
+
}
|
|
100
|
+
else if (priceVsSma !== null) {
|
|
101
|
+
if (priceVsSma > 0.1)
|
|
102
|
+
direction = 'bullish';
|
|
103
|
+
else if (priceVsSma < -0.1)
|
|
104
|
+
direction = 'bearish';
|
|
105
|
+
}
|
|
106
|
+
// Regime: trending vs ranging
|
|
107
|
+
let regime = 'ranging';
|
|
108
|
+
if (closes.length >= 10 && sma20 !== null) {
|
|
109
|
+
const recentCloses = closes.slice(-10);
|
|
110
|
+
const avgDeviation = recentCloses.reduce((s, c) => s + Math.abs((c - sma20) / sma20), 0) / recentCloses.length;
|
|
111
|
+
regime = avgDeviation > 0.002 ? 'trending' : 'ranging';
|
|
112
|
+
}
|
|
113
|
+
// Momentum
|
|
114
|
+
let momentum = 'weak';
|
|
115
|
+
if (rsiVal !== null) {
|
|
116
|
+
const rsiDist = Math.abs(rsiVal - 50);
|
|
117
|
+
if (rsiDist > 20)
|
|
118
|
+
momentum = 'strong';
|
|
119
|
+
else if (rsiDist > 10)
|
|
120
|
+
momentum = 'moderate';
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
timeframe,
|
|
124
|
+
direction,
|
|
125
|
+
regime,
|
|
126
|
+
momentum,
|
|
127
|
+
rsiValue: rsiVal !== null ? +rsiVal.toFixed(1) : null,
|
|
128
|
+
priceVsSma: priceVsSma !== null ? +priceVsSma.toFixed(3) : null,
|
|
129
|
+
emaFast: emaFastVal !== null ? +emaFastVal.toFixed(2) : null,
|
|
130
|
+
emaSlow: emaSlowVal !== null ? +emaSlowVal.toFixed(2) : null,
|
|
131
|
+
changePercent: changePercent !== null ? +changePercent.toFixed(3) : null,
|
|
132
|
+
candles: closes.length,
|
|
133
|
+
atr14: atr14 !== null ? +atr14.toFixed(2) : undefined,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// --- Tool implementation ---
|
|
137
|
+
export async function getMarketStructureTool(args, deps) {
|
|
138
|
+
const symbol = args.symbol;
|
|
139
|
+
const requestedTFs = args.timeframes
|
|
140
|
+
? TIMEFRAMES.filter((tf) => args.timeframes.includes(tf.id))
|
|
141
|
+
: TIMEFRAMES;
|
|
142
|
+
if (requestedTFs.length === 0) {
|
|
143
|
+
return { error: `No valid timeframes. Available: ${TIMEFRAMES.map((t) => t.id).join(', ')}` };
|
|
144
|
+
}
|
|
145
|
+
// Fetch all timeframes in parallel
|
|
146
|
+
const results = await Promise.allSettled(requestedTFs.map(async (tf) => {
|
|
147
|
+
const candles = await deps.binanceApi.fetchOHLCV(symbol, tf.ccxt, tf.limit);
|
|
148
|
+
if (!candles || candles.length === 0) {
|
|
149
|
+
return { tf: tf.id, analysis: null };
|
|
150
|
+
}
|
|
151
|
+
return { tf: tf.id, analysis: analyzeCandles(tf.id, candles) };
|
|
152
|
+
}));
|
|
153
|
+
const timeframes = [];
|
|
154
|
+
for (const r of results) {
|
|
155
|
+
if (r.status === 'fulfilled' && r.value.analysis) {
|
|
156
|
+
timeframes.push(r.value.analysis);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (timeframes.length === 0) {
|
|
160
|
+
return { error: `Failed to fetch candle data for ${symbol}` };
|
|
161
|
+
}
|
|
162
|
+
// Compute alignment
|
|
163
|
+
const withData = timeframes.filter((t) => t.candles >= 5);
|
|
164
|
+
const bullish = withData.filter((t) => t.direction === 'bullish').length;
|
|
165
|
+
const bearish = withData.filter((t) => t.direction === 'bearish').length;
|
|
166
|
+
const neutral = withData.filter((t) => t.direction === 'neutral').length;
|
|
167
|
+
const total = withData.length;
|
|
168
|
+
let summary;
|
|
169
|
+
if (bullish === total)
|
|
170
|
+
summary = 'All timeframes bullish — strong alignment';
|
|
171
|
+
else if (bearish === total)
|
|
172
|
+
summary = 'All timeframes bearish — strong alignment';
|
|
173
|
+
else if (bullish >= total - 1)
|
|
174
|
+
summary = `${bullish}/${total} bullish — mostly aligned up`;
|
|
175
|
+
else if (bearish >= total - 1)
|
|
176
|
+
summary = `${bearish}/${total} bearish — mostly aligned down`;
|
|
177
|
+
else
|
|
178
|
+
summary = 'Mixed signals — timeframes conflicting';
|
|
179
|
+
// Extract 1h ATR for top-level summary (primary risk reference timeframe)
|
|
180
|
+
const hourlyTf = timeframes.find(t => t.timeframe === '1h');
|
|
181
|
+
const lastClose = hourlyTf?.emaFast ?? null; // approximate; use actual last close below
|
|
182
|
+
const hourlyAtr14 = hourlyTf?.atr14;
|
|
183
|
+
// Compute atrPercent relative to last close of the 1h timeframe
|
|
184
|
+
let topLevelAtr;
|
|
185
|
+
if (hourlyAtr14 !== undefined && hourlyTf) {
|
|
186
|
+
// Use emaSlow as a stable price reference (if available), else emaFast
|
|
187
|
+
const refPrice = hourlyTf.emaSlow ?? hourlyTf.emaFast ?? 0;
|
|
188
|
+
const atrPct = refPrice > 0 ? (hourlyAtr14 / refPrice) * 100 : 0;
|
|
189
|
+
topLevelAtr = { atr14: hourlyAtr14, atrPercent: +atrPct.toFixed(4) };
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
symbol,
|
|
193
|
+
timeframes,
|
|
194
|
+
alignment: { bullish, bearish, neutral, total, summary },
|
|
195
|
+
atr: topLevelAtr,
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PositionDecisionsClient, MyMinedPattern } from '../ingest/position-decisions-client.js';
|
|
2
|
+
export interface GetMyMinedPatternsArgs {
|
|
3
|
+
within_days?: number;
|
|
4
|
+
min_sample_size?: number;
|
|
5
|
+
max_p_value?: number;
|
|
6
|
+
include_promoted?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface GetMyMinedPatternsDeps {
|
|
9
|
+
decisionsClient?: PositionDecisionsClient;
|
|
10
|
+
userId?: string;
|
|
11
|
+
}
|
|
12
|
+
export type GetMyMinedPatternsResult = {
|
|
13
|
+
ok: true;
|
|
14
|
+
patterns: MyMinedPattern[];
|
|
15
|
+
total_returned: number;
|
|
16
|
+
note?: string;
|
|
17
|
+
} | {
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function getMyMinedPatternsTool(args: GetMyMinedPatternsArgs, deps: GetMyMinedPatternsDeps): Promise<GetMyMinedPatternsResult>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Tool: get_my_mined_patterns — Phase 4.5 self-assessment read.
|
|
2
|
+
//
|
|
3
|
+
// Returns the agent's own statistically-significant patterns from its trade
|
|
4
|
+
// history, surfaced by the daily pattern-mining cron. The agent uses this to
|
|
5
|
+
// decide which patterns are worth proposing as learnings (via propose_learning).
|
|
6
|
+
//
|
|
7
|
+
// Agency-preserving READ surface: no validation, no gating. The agent reads
|
|
8
|
+
// statistics, the agent decides. Per feedback_agent_agency_over_hardcoding.
|
|
9
|
+
import { logger, formatError } from '../logger.js';
|
|
10
|
+
const TAG = 'get-my-mined-patterns';
|
|
11
|
+
export async function getMyMinedPatternsTool(args, deps) {
|
|
12
|
+
if (!deps.decisionsClient || !deps.userId) {
|
|
13
|
+
return {
|
|
14
|
+
ok: true,
|
|
15
|
+
patterns: [],
|
|
16
|
+
total_returned: 0,
|
|
17
|
+
note: 'Position-decisions ingest is not configured; no mined patterns available.',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Defensive coercion — JSON-Schema validation upstream covers types, but
|
|
21
|
+
// out-of-range numbers (e.g. within_days=999) should clamp not error.
|
|
22
|
+
const within = clampInt(args.within_days, 1, 90, 14);
|
|
23
|
+
const minN = clampInt(args.min_sample_size, 1, 100_000, 20);
|
|
24
|
+
const maxP = clampFloat(args.max_p_value, 0, 1, 0.05);
|
|
25
|
+
let response;
|
|
26
|
+
try {
|
|
27
|
+
response = await deps.decisionsClient.getMyMinedPatterns(deps.userId, {
|
|
28
|
+
withinDays: within,
|
|
29
|
+
minSampleSize: minN,
|
|
30
|
+
maxPValue: maxP,
|
|
31
|
+
includePromoted: !!args.include_promoted,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
logger.warn(TAG, `getMyMinedPatterns threw: ${formatError(err)}`);
|
|
36
|
+
return { error: 'Failed to fetch mined patterns from webapp.' };
|
|
37
|
+
}
|
|
38
|
+
if (!response) {
|
|
39
|
+
return {
|
|
40
|
+
ok: true,
|
|
41
|
+
patterns: [],
|
|
42
|
+
total_returned: 0,
|
|
43
|
+
note: 'Webapp /api/internal/learnings/my-patterns returned no result; proceeding without mined-pattern data.',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
ok: true,
|
|
48
|
+
patterns: response.patterns,
|
|
49
|
+
total_returned: response.totalReturned,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function clampInt(v, lo, hi, def) {
|
|
53
|
+
if (typeof v !== 'number' || Number.isNaN(v))
|
|
54
|
+
return def;
|
|
55
|
+
return Math.max(lo, Math.min(hi, Math.trunc(v)));
|
|
56
|
+
}
|
|
57
|
+
function clampFloat(v, lo, hi, def) {
|
|
58
|
+
if (typeof v !== 'number' || Number.isNaN(v))
|
|
59
|
+
return def;
|
|
60
|
+
return Math.max(lo, Math.min(hi, v));
|
|
61
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PositionDecisionsClient, MyProposedLearning } from '../ingest/position-decisions-client.js';
|
|
2
|
+
export interface GetMyProposedLearningsArgs {
|
|
3
|
+
include_hypothesis?: boolean;
|
|
4
|
+
include_confirmed?: boolean;
|
|
5
|
+
include_retired?: boolean;
|
|
6
|
+
proposed_by?: 'agent' | 'operator';
|
|
7
|
+
}
|
|
8
|
+
export interface GetMyProposedLearningsDeps {
|
|
9
|
+
decisionsClient?: PositionDecisionsClient;
|
|
10
|
+
userId?: string;
|
|
11
|
+
}
|
|
12
|
+
export type GetMyProposedLearningsResult = {
|
|
13
|
+
ok: true;
|
|
14
|
+
learnings: MyProposedLearning[];
|
|
15
|
+
total_returned: number;
|
|
16
|
+
note?: string;
|
|
17
|
+
} | {
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function getMyProposedLearningsTool(args: GetMyProposedLearningsArgs, deps: GetMyProposedLearningsDeps): Promise<GetMyProposedLearningsResult>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Tool: get_my_proposed_learnings — Phase 4.5 self-audit read.
|
|
2
|
+
//
|
|
3
|
+
// Returns this user's own learnings across confidence states (hypothesis,
|
|
4
|
+
// confirmed, retired) so the agent can dedup before proposing a new
|
|
5
|
+
// hypothesis. Distinct from get_relevant_learnings, which returns only
|
|
6
|
+
// `confirmed` rows matching the current decision context.
|
|
7
|
+
//
|
|
8
|
+
// Agency-preserving READ surface.
|
|
9
|
+
import { logger, formatError } from '../logger.js';
|
|
10
|
+
const TAG = 'get-my-proposed-learnings';
|
|
11
|
+
export async function getMyProposedLearningsTool(args, deps) {
|
|
12
|
+
if (!deps.decisionsClient || !deps.userId) {
|
|
13
|
+
return {
|
|
14
|
+
ok: true,
|
|
15
|
+
learnings: [],
|
|
16
|
+
total_returned: 0,
|
|
17
|
+
note: 'Position-decisions ingest is not configured; no learnings available.',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Treat missing fields as defaults (hypothesis ON, confirmed ON, retired OFF).
|
|
21
|
+
const includeHypothesis = args.include_hypothesis !== false;
|
|
22
|
+
const includeConfirmed = args.include_confirmed !== false;
|
|
23
|
+
const includeRetired = !!args.include_retired;
|
|
24
|
+
if (!includeHypothesis && !includeConfirmed && !includeRetired) {
|
|
25
|
+
return {
|
|
26
|
+
error: 'At least one of include_hypothesis, include_confirmed, include_retired must be true.',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let response;
|
|
30
|
+
try {
|
|
31
|
+
response = await deps.decisionsClient.getMyProposedLearnings(deps.userId, {
|
|
32
|
+
includeHypothesis,
|
|
33
|
+
includeConfirmed,
|
|
34
|
+
includeRetired,
|
|
35
|
+
proposedBy: args.proposed_by,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
logger.warn(TAG, `getMyProposedLearnings threw: ${formatError(err)}`);
|
|
40
|
+
return { error: 'Failed to fetch proposed learnings from webapp.' };
|
|
41
|
+
}
|
|
42
|
+
if (!response) {
|
|
43
|
+
return {
|
|
44
|
+
ok: true,
|
|
45
|
+
learnings: [],
|
|
46
|
+
total_returned: 0,
|
|
47
|
+
note: 'Webapp /api/internal/learnings/my-proposals returned no result.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
learnings: response.learnings,
|
|
53
|
+
total_returned: response.totalReturned,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PositionDecisionsClient, PositionReviewContext } from '../ingest/position-decisions-client.js';
|
|
2
|
+
import type { PositionStateStore } from '../live/position-state-store.js';
|
|
3
|
+
export interface GetMyRecentReviewsArgs {
|
|
4
|
+
/** Optional — when provided, returns context only for that symbol. When
|
|
5
|
+
* omitted, returns context for every currently-open position. */
|
|
6
|
+
symbol?: string;
|
|
7
|
+
/** Number of reviews to include per position (1–10, default 3). */
|
|
8
|
+
review_limit?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface GetMyRecentReviewsDeps {
|
|
11
|
+
decisionsClient?: PositionDecisionsClient;
|
|
12
|
+
stateStore?: PositionStateStore;
|
|
13
|
+
userId?: string;
|
|
14
|
+
}
|
|
15
|
+
export type GetMyRecentReviewsResult = {
|
|
16
|
+
ok: true;
|
|
17
|
+
positions: PositionReviewContext[];
|
|
18
|
+
note?: string;
|
|
19
|
+
} | {
|
|
20
|
+
error: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function getMyRecentReviewsTool(args: GetMyRecentReviewsArgs, deps: GetMyRecentReviewsDeps): Promise<GetMyRecentReviewsResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Tool: get_my_recent_reviews — Phase 1 of the Agent Learning Loop.
|
|
2
|
+
//
|
|
3
|
+
// Returns, for each currently-open position (or just the requested symbol),
|
|
4
|
+
// the agent's last N reviews + a verdict-streak counter + recent same-symbol
|
|
5
|
+
// re-entry context. The agent calls this BEFORE record_position_reviews each
|
|
6
|
+
// heartbeat so it can reference its prior reasoning instead of re-deriving in
|
|
7
|
+
// isolation. Without this surface the journal is write-only — the agent files
|
|
8
|
+
// reviews but never reads them, and "stale hold" remains structurally invisible
|
|
9
|
+
// (the WIF 8.7h-underwater pattern from 2026-05-03).
|
|
10
|
+
//
|
|
11
|
+
// Resolves position UUIDs from the local PositionStateStore so callers don't
|
|
12
|
+
// need to know about webappPositionId — the tool only returns context for
|
|
13
|
+
// positions currently open on this plugin instance.
|
|
14
|
+
//
|
|
15
|
+
// See docs/AGENT_LEARNING_LOOP_PLAN.md §3 Phase 1.
|
|
16
|
+
import { logger, formatError } from '../logger.js';
|
|
17
|
+
import { normalizeBracketSymbol } from '../live/bracket-ledger.js';
|
|
18
|
+
const TAG = 'get-my-recent-reviews';
|
|
19
|
+
const DEFAULT_LIMIT = 3;
|
|
20
|
+
const MAX_LIMIT = 10;
|
|
21
|
+
export async function getMyRecentReviewsTool(args, deps) {
|
|
22
|
+
if (!deps.decisionsClient || !deps.userId || !deps.stateStore) {
|
|
23
|
+
// Mirrors record_position_reviews off-mode behaviour — tool callable, but
|
|
24
|
+
// without ingest wiring there is nothing to read.
|
|
25
|
+
return {
|
|
26
|
+
ok: true,
|
|
27
|
+
positions: [],
|
|
28
|
+
note: 'Position-decisions ingest is not configured; no reviews available.',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const limit = clampLimit(args.review_limit);
|
|
32
|
+
const allEntries = deps.stateStore.getAll();
|
|
33
|
+
const eligible = allEntries.filter((e) => typeof e.webappPositionId === 'string');
|
|
34
|
+
const targets = args.symbol
|
|
35
|
+
? eligible.filter((e) => e.symbol === normalizeBracketSymbol(args.symbol))
|
|
36
|
+
: eligible;
|
|
37
|
+
if (targets.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
positions: [],
|
|
41
|
+
note: args.symbol
|
|
42
|
+
? `No open position for ${args.symbol} (or it has not yet upserted to webapp).`
|
|
43
|
+
: 'No open positions with persisted webapp ids.',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const positionIds = targets.map((e) => e.webappPositionId);
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await deps.decisionsClient.getRecentReviews(deps.userId, positionIds, limit);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
logger.warn(TAG, `getRecentReviews threw: ${formatError(err)}`);
|
|
53
|
+
return { error: 'Failed to fetch reviews from webapp.' };
|
|
54
|
+
}
|
|
55
|
+
if (!response) {
|
|
56
|
+
return { error: 'Webapp /api/internal/position-reviews/recent returned no result.' };
|
|
57
|
+
}
|
|
58
|
+
return { ok: true, positions: response.positions };
|
|
59
|
+
}
|
|
60
|
+
function clampLimit(raw) {
|
|
61
|
+
if (raw === undefined || raw === null)
|
|
62
|
+
return DEFAULT_LIMIT;
|
|
63
|
+
if (!Number.isFinite(raw) || raw <= 0)
|
|
64
|
+
return DEFAULT_LIMIT;
|
|
65
|
+
return Math.min(Math.floor(raw), MAX_LIMIT);
|
|
66
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BinancePublicApi } from '../ccxt/binance-public.js';
|
|
2
|
+
export interface OrderbookResult {
|
|
3
|
+
symbol: string;
|
|
4
|
+
bids: [number, number][];
|
|
5
|
+
asks: [number, number][];
|
|
6
|
+
midPrice: number;
|
|
7
|
+
spread: number;
|
|
8
|
+
spreadBps: number;
|
|
9
|
+
bidVolume: number;
|
|
10
|
+
askVolume: number;
|
|
11
|
+
imbalance: number;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function getOrderbookTool(args: {
|
|
15
|
+
symbol: string;
|
|
16
|
+
depth?: number;
|
|
17
|
+
}, deps: {
|
|
18
|
+
binanceApi: BinancePublicApi;
|
|
19
|
+
}): Promise<OrderbookResult | {
|
|
20
|
+
error: string;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Tool: get_orderbook — fetches L2 order book depth from Binance.
|
|
2
|
+
// Returns top N bids/asks with prices and quantities, plus computed
|
|
3
|
+
// metrics: spread, mid-price, bid/ask volume, imbalance ratio.
|
|
4
|
+
export async function getOrderbookTool(args, deps) {
|
|
5
|
+
const { symbol, depth = 20 } = args;
|
|
6
|
+
const limit = Math.min(Math.max(depth, 5), 100);
|
|
7
|
+
const book = await deps.binanceApi.fetchOrderBook(symbol, limit);
|
|
8
|
+
if (!book || book.bids.length === 0 || book.asks.length === 0) {
|
|
9
|
+
return { error: `Failed to fetch order book for ${symbol}` };
|
|
10
|
+
}
|
|
11
|
+
const bestBid = book.bids[0][0];
|
|
12
|
+
const bestAsk = book.asks[0][0];
|
|
13
|
+
const midPrice = (bestBid + bestAsk) / 2;
|
|
14
|
+
const spread = bestAsk - bestBid;
|
|
15
|
+
const spreadBps = (spread / midPrice) * 10_000;
|
|
16
|
+
const bidVolume = book.bids.reduce((sum, [, qty]) => sum + qty, 0);
|
|
17
|
+
const askVolume = book.asks.reduce((sum, [, qty]) => sum + qty, 0);
|
|
18
|
+
const totalVolume = bidVolume + askVolume;
|
|
19
|
+
const imbalance = totalVolume > 0 ? (bidVolume - askVolume) / totalVolume : 0;
|
|
20
|
+
return {
|
|
21
|
+
symbol,
|
|
22
|
+
bids: book.bids,
|
|
23
|
+
asks: book.asks,
|
|
24
|
+
midPrice: +midPrice.toFixed(2),
|
|
25
|
+
spread: +spread.toFixed(2),
|
|
26
|
+
spreadBps: +spreadBps.toFixed(2),
|
|
27
|
+
bidVolume: +bidVolume.toFixed(4),
|
|
28
|
+
askVolume: +askVolume.toFixed(4),
|
|
29
|
+
imbalance: +imbalance.toFixed(4),
|
|
30
|
+
timestamp: new Date().toISOString(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Tool: get_pattern_scan — automated chart pattern recognition from OHLCV data.
|
|
2
|
+
// Calls Intelligence API for swing points, trendlines, and 10 pattern types.
|
|
3
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
4
|
+
export async function getPatternScanTool(args, deps) {
|
|
5
|
+
const intelSymbol = args.symbol.replace('/', '');
|
|
6
|
+
const timeframe = args.timeframe ?? '1h';
|
|
7
|
+
return fetchIntelApi(`/api/patterns/${enc(intelSymbol)}?timeframe=${enc(timeframe)}&lookback=200`, deps);
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Tool: get_regime — fetches regime classification from the Intelligence API.
|
|
2
|
+
// Returns current market regime (trend_up, trend_down, range_tight, etc.)
|
|
3
|
+
// with confidence, probabilities, and contributing features.
|
|
4
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
5
|
+
export async function getRegimeTool(args, deps) {
|
|
6
|
+
return fetchIntelApi(`/api/regime/${enc(args.symbol)}`, deps);
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PositionDecisionsClient, RelevantLearning } from '../ingest/position-decisions-client.js';
|
|
2
|
+
export interface GetRelevantLearningsArgs {
|
|
3
|
+
applies_at: 'entry' | 'heartbeat' | 'close';
|
|
4
|
+
setup_type?: string;
|
|
5
|
+
regime?: string;
|
|
6
|
+
verdict?: 'hold' | 'add_on' | 'close_recommended';
|
|
7
|
+
}
|
|
8
|
+
export interface GetRelevantLearningsDeps {
|
|
9
|
+
decisionsClient?: PositionDecisionsClient;
|
|
10
|
+
userId?: string;
|
|
11
|
+
}
|
|
12
|
+
export type GetRelevantLearningsResult = {
|
|
13
|
+
ok: true;
|
|
14
|
+
learnings: RelevantLearning[];
|
|
15
|
+
total_candidates: number;
|
|
16
|
+
returned_count: number;
|
|
17
|
+
note?: string;
|
|
18
|
+
} | {
|
|
19
|
+
error: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function getRelevantLearningsTool(args: GetRelevantLearningsArgs, deps: GetRelevantLearningsDeps): Promise<GetRelevantLearningsResult>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Tool: get_relevant_learnings — Phase 4 of the Agent Learning Loop.
|
|
2
|
+
//
|
|
3
|
+
// Returns operator-curated `confirmed` learnings whose trigger_condition
|
|
4
|
+
// matches the current decision context. The agent calls this at decision
|
|
5
|
+
// time (before create_order, before record_position_reviews, before
|
|
6
|
+
// close_position) to read the small set of curated rules that apply NOW.
|
|
7
|
+
//
|
|
8
|
+
// Output capped at 5 learnings per call (server-side enforced) — per
|
|
9
|
+
// docs/AGENT_LEARNING_LOOP_PLAN.md §7 cognitive-load mitigation.
|
|
10
|
+
//
|
|
11
|
+
// Agency-preserving: this is a READ surface. The agent reads, the agent
|
|
12
|
+
// decides. There is no validator that forces compliance. Per
|
|
13
|
+
// memory/feedback_agent_agency_over_hardcoding.md, learnings inform,
|
|
14
|
+
// they don't gate.
|
|
15
|
+
import { logger, formatError } from '../logger.js';
|
|
16
|
+
const TAG = 'get-relevant-learnings';
|
|
17
|
+
const VALID_APPLIES_AT = new Set(['entry', 'heartbeat', 'close']);
|
|
18
|
+
export async function getRelevantLearningsTool(args, deps) {
|
|
19
|
+
if (!deps.decisionsClient || !deps.userId) {
|
|
20
|
+
// Ingest not wired — return empty, callable. Same shape as
|
|
21
|
+
// get_my_recent_reviews under the same condition.
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
learnings: [],
|
|
25
|
+
total_candidates: 0,
|
|
26
|
+
returned_count: 0,
|
|
27
|
+
note: 'Position-decisions ingest is not configured; no learnings available.',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (!args.applies_at || !VALID_APPLIES_AT.has(args.applies_at)) {
|
|
31
|
+
return {
|
|
32
|
+
error: "applies_at is required and must be one of: 'entry', 'heartbeat', 'close'.",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
let response;
|
|
36
|
+
try {
|
|
37
|
+
response = await deps.decisionsClient.getRelevantLearnings(deps.userId, {
|
|
38
|
+
appliesAt: args.applies_at,
|
|
39
|
+
setupType: args.setup_type,
|
|
40
|
+
regime: args.regime,
|
|
41
|
+
verdict: args.verdict,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
logger.warn(TAG, `getRelevantLearnings threw: ${formatError(err)}`);
|
|
46
|
+
return { error: 'Failed to fetch learnings from webapp.' };
|
|
47
|
+
}
|
|
48
|
+
if (!response) {
|
|
49
|
+
// Network/server failure — degrade quietly to empty. The agent should
|
|
50
|
+
// proceed; missing learnings is not a hard stop.
|
|
51
|
+
return {
|
|
52
|
+
ok: true,
|
|
53
|
+
learnings: [],
|
|
54
|
+
total_candidates: 0,
|
|
55
|
+
returned_count: 0,
|
|
56
|
+
note: 'Webapp /api/internal/learnings returned no result; proceeding without curated learnings.',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
learnings: response.learnings,
|
|
62
|
+
total_candidates: response.totalCandidates,
|
|
63
|
+
returned_count: response.returnedCount,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Tool: get_resting_liquidity — banded resting limit-order liquidity for a
|
|
2
|
+
// symbol, sourced from the latest order_book_snapshots row. Returns USD
|
|
3
|
+
// notional resting on each side at the 0.2 / 0.5 / 1.0 / 1.5 % bands plus
|
|
4
|
+
// `coverage_pct` (the % range depth-20 actually reaches on this symbol).
|
|
5
|
+
//
|
|
6
|
+
// Returns `{ enabled: false, reason }` when the intel-side
|
|
7
|
+
// `microstructure.bandedLiquidity` flag is off (= columns are NULL).
|
|
8
|
+
import { fetchIntelApi, enc } from './intel-api.js';
|
|
9
|
+
export async function getRestingLiquidityTool(args, deps) {
|
|
10
|
+
return fetchIntelApi(`/api/resting-liquidity/${enc(args.symbol)}`, deps);
|
|
11
|
+
}
|