@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,415 @@
|
|
|
1
|
+
// Assessment-schema validators for agent tool calls (SKILL.md v2.10.0).
|
|
2
|
+
//
|
|
3
|
+
// The LLM's value is context-sensitive reasoning. We don't want to gate
|
|
4
|
+
// trading tools behind time floors (a thesis CAN legitimately invalidate
|
|
5
|
+
// in 60 seconds). We want to gate them behind PROOF that the reasoning
|
|
6
|
+
// happened — a structured assessment the agent has to fill from real
|
|
7
|
+
// scorecard data. If it filled the fields, the reasoning happened. If
|
|
8
|
+
// it didn't, we reject.
|
|
9
|
+
//
|
|
10
|
+
// Used by:
|
|
11
|
+
// - close-position.ts — requires `reason` + `assessment` on every call.
|
|
12
|
+
// - create-order.ts — requires thesis + regime + confluence_score in
|
|
13
|
+
// live mode.
|
|
14
|
+
// Tested in: assessment-validation.test.ts.
|
|
15
|
+
import { normalizeBracketSymbol } from '../live/bracket-ledger.js';
|
|
16
|
+
// ---- close_position ----
|
|
17
|
+
/** The five legitimate reasons to manually close an otherwise-bracketed
|
|
18
|
+
* position (see SKILL.md v2.10.0 Position Management Discipline). Any other
|
|
19
|
+
* value is rejected. */
|
|
20
|
+
export const CLOSE_REASONS = [
|
|
21
|
+
'regime_flip',
|
|
22
|
+
// 'scorecard_reversal' — DEPRECATED 2026-06-16 (exit-discipline #2): a lower re-scored
|
|
23
|
+
// confluence is anti-predictive of outcome and is NOT a valid exit trigger. Removed from
|
|
24
|
+
// the menu; validateClosePosition rejects it early with a redirect to objective reasons.
|
|
25
|
+
'risk_limit',
|
|
26
|
+
'bracket_integrity',
|
|
27
|
+
'operator_command',
|
|
28
|
+
];
|
|
29
|
+
function nonEmptyString(v) {
|
|
30
|
+
return typeof v === 'string' && v.trim().length > 0;
|
|
31
|
+
}
|
|
32
|
+
function isScore(v) {
|
|
33
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 10;
|
|
34
|
+
}
|
|
35
|
+
/** Validate a `close_position` call against the SKILL.md v2.10.0 contract.
|
|
36
|
+
* Returns {ok:true} to proceed or {ok:false, error} to reject the tool call.
|
|
37
|
+
*
|
|
38
|
+
* Philosophy: force the agent to submit structured proof that a real
|
|
39
|
+
* reassessment happened. No time gate — a 60-second close is legal if the
|
|
40
|
+
* assessment holds up. But blank or inconsistent fields mean no reasoning,
|
|
41
|
+
* so we refuse.
|
|
42
|
+
*/
|
|
43
|
+
export function validateClosePosition(args) {
|
|
44
|
+
if (!nonEmptyString(args.symbol)) {
|
|
45
|
+
return { ok: false, error: 'Symbol is required.' };
|
|
46
|
+
}
|
|
47
|
+
// `reason` is required. Enum check first.
|
|
48
|
+
if (!args.reason) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: `close_position requires a 'reason' param (one of: ${CLOSE_REASONS.join(', ')}). ` +
|
|
52
|
+
'See SKILL.md v2.10.0 Position Management Discipline.',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// scorecard_reversal DEPRECATED 2026-06-16 (exit-discipline #2). The entry scorecard is
|
|
56
|
+
// anti-predictive of MFE, so re-scoring an open position and exiting on a lower score cut
|
|
57
|
+
// undeveloped trades for no edge (108 such closes, 82% never developed). The exit gate
|
|
58
|
+
// already blocks premature exits by MFE/current_r regardless of reason; this closes the
|
|
59
|
+
// agent-facing menu. See docs/EXIT_DISCIPLINE_BUILD_PLAN.md Step 1 (#2).
|
|
60
|
+
if (args.reason === 'scorecard_reversal') {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: "close reason 'scorecard_reversal' is deprecated — a lower re-scored confluence is not a valid " +
|
|
64
|
+
'exit trigger (the entry score is anti-predictive of outcome). Exit on an objective trigger instead: ' +
|
|
65
|
+
"'regime_flip' (name the regime change + contradicting_metric), 'risk_limit', a pinned-invalidation hit, " +
|
|
66
|
+
"or 'operator_command'. If the thesis genuinely broke, use 'regime_flip' and name the signal that flipped.",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (!CLOSE_REASONS.includes(args.reason)) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: `Invalid close reason: '${args.reason}'. Must be one of: ${CLOSE_REASONS.join(', ')}.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const reason = args.reason;
|
|
76
|
+
// Operator commands bypass the assessment requirement — operator typing
|
|
77
|
+
// "close ADA now" via chat doesn't need structured justification.
|
|
78
|
+
if (reason === 'operator_command') {
|
|
79
|
+
return { ok: true };
|
|
80
|
+
}
|
|
81
|
+
// All other reasons need an assessment object.
|
|
82
|
+
const a = args.assessment;
|
|
83
|
+
if (!a || typeof a !== 'object') {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
error: `close_position with reason='${reason}' requires an 'assessment' object. ` +
|
|
87
|
+
'Include entry_score, current_score, entry_regime, current_regime, and contradicting_metric.',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Reason-specific consistency checks.
|
|
91
|
+
switch (reason) {
|
|
92
|
+
// scorecard_reversal handled earlier by the deprecation reject — no case here.
|
|
93
|
+
case 'regime_flip': {
|
|
94
|
+
if (!nonEmptyString(a.entry_regime)) {
|
|
95
|
+
return { ok: false, error: 'regime_flip requires assessment.entry_regime.' };
|
|
96
|
+
}
|
|
97
|
+
if (!nonEmptyString(a.current_regime)) {
|
|
98
|
+
return { ok: false, error: 'regime_flip requires assessment.current_regime.' };
|
|
99
|
+
}
|
|
100
|
+
if (a.entry_regime === a.current_regime) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
error: `regime_flip requires current_regime to differ from entry_regime. ` +
|
|
104
|
+
`Got entry_regime=current_regime='${a.entry_regime}'. ` +
|
|
105
|
+
'If regime is unchanged but you still want to close, this is probably "HOLD" per the decision matrix.',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (!nonEmptyString(a.contradicting_metric)) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: 'regime_flip requires assessment.contradicting_metric describing what changed ' +
|
|
112
|
+
'(e.g., "multi-TF alignment flipped from RANGE_WIDE to TREND_DOWN with 4h MACD cross").',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return { ok: true };
|
|
116
|
+
}
|
|
117
|
+
case 'risk_limit': {
|
|
118
|
+
// Risk-limit triggers are time-sensitive; require a terse justification
|
|
119
|
+
// but not the full scorecard.
|
|
120
|
+
if (!nonEmptyString(a.contradicting_metric)) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
error: 'risk_limit requires assessment.contradicting_metric naming the metric at threshold ' +
|
|
124
|
+
'(e.g., "drawdown utilisation 82% — AMBER zone", "gross exposure 1.9x NAV").',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { ok: true };
|
|
128
|
+
}
|
|
129
|
+
case 'bracket_integrity': {
|
|
130
|
+
// Bracket reconciliation edge cases — expect the operator to describe the
|
|
131
|
+
// condition (e.g., ledger_terminal_no_order + attach_brackets failed).
|
|
132
|
+
if (!nonEmptyString(a.contradicting_metric)) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
error: 'bracket_integrity requires assessment.contradicting_metric with the audit output ' +
|
|
136
|
+
'(e.g., "audit_bracket_protection has_stop=false + attach_brackets returned ledger_terminal_no_order after 3 retries").',
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
return { ok: true };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { ok: true };
|
|
143
|
+
}
|
|
144
|
+
/** Validate a `create_order` call against the SKILL.md v2.10.0 contract.
|
|
145
|
+
* Only enforced in LIVE mode — paper mode is for exploration and allows
|
|
146
|
+
* missing metadata so the agent can probe without full reasoning. Live
|
|
147
|
+
* mode = real money = must show your work.
|
|
148
|
+
*/
|
|
149
|
+
export function validateCreateOrder(args, isLive) {
|
|
150
|
+
if (!isLive)
|
|
151
|
+
return { ok: true };
|
|
152
|
+
if (!nonEmptyString(args.thesis) || args.thesis.length < 20) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: 'create_order in live mode requires a thesis of at least 20 characters. ' +
|
|
156
|
+
'Briefly describe setup, regime, and the signal driving the entry.',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (!nonEmptyString(args.setup_type)) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
error: "create_order in live mode requires setup_type (e.g., 'pullback_to_ema', 'vwap_reversion', 'breakout'). " +
|
|
163
|
+
'The SKILL.md decision matrix expects it at heartbeat reassessment time.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (!nonEmptyString(args.regime)) {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
error: 'create_order in live mode requires regime (e.g., RANGE_WIDE, TREND_UP). ' +
|
|
170
|
+
'Heartbeat reassessment needs this to detect regime flips.',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (typeof args.regime_confidence !== 'number' ||
|
|
174
|
+
!Number.isFinite(args.regime_confidence) ||
|
|
175
|
+
args.regime_confidence < 0 ||
|
|
176
|
+
args.regime_confidence > 100) {
|
|
177
|
+
return {
|
|
178
|
+
ok: false,
|
|
179
|
+
error: 'create_order in live mode requires regime_confidence as a number in [0, 100].',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (!isScore(args.confluence_score)) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
error: 'create_order in live mode requires confluence_score as a number in [0, 10]. ' +
|
|
186
|
+
'Heartbeat reassessment needs this to detect scorecard reversal.',
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const validVerdicts = new Set(['STRONG_GO', 'GO', 'MARGINAL', 'NO_GO']);
|
|
190
|
+
if (!nonEmptyString(args.scorecard_verdict) || !validVerdicts.has(args.scorecard_verdict)) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
error: `create_order in live mode requires scorecard_verdict in ${[...validVerdicts].join('/')}. ` +
|
|
194
|
+
"If you don't have a clear verdict, you don't have the setup — skip the trade.",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// Pinned plan — validate-if-present only (NOT required yet; we add the
|
|
198
|
+
// capability + indication first, per the 2026-06-05 governing principle).
|
|
199
|
+
if (args.invalidation_price != null &&
|
|
200
|
+
(typeof args.invalidation_price !== 'number' ||
|
|
201
|
+
!Number.isFinite(args.invalidation_price) ||
|
|
202
|
+
args.invalidation_price <= 0)) {
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
error: 'create_order: invalidation_price, if provided, must be a positive number — ' +
|
|
206
|
+
'the price at which the entry thesis is wrong (your pinned "I am wrong here" level).',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return { ok: true };
|
|
210
|
+
}
|
|
211
|
+
const REALIZATION_RULE_TYPES = new Set(['fixed_target', 'trail', 'scale', 'manual']);
|
|
212
|
+
/** Coerce the agent-supplied `realization_rule` (snake_case from the tool
|
|
213
|
+
* schema) into a clean RealizationRule, dropping garbage. Permissive by
|
|
214
|
+
* design — this is the agent's stated PLAN (indication), not an enforced
|
|
215
|
+
* mechanic, so we keep whatever is well-formed and default an unknown `type`
|
|
216
|
+
* to 'manual' rather than rejecting. Returns undefined for non-objects. */
|
|
217
|
+
export function sanitizeRealizationRule(raw) {
|
|
218
|
+
if (!raw || typeof raw !== 'object')
|
|
219
|
+
return undefined;
|
|
220
|
+
const r = raw;
|
|
221
|
+
const fin = (v) => typeof v === 'number' && Number.isFinite(v) ? v : undefined;
|
|
222
|
+
const type = typeof r.type === 'string' && REALIZATION_RULE_TYPES.has(r.type)
|
|
223
|
+
? r.type
|
|
224
|
+
: 'manual';
|
|
225
|
+
const out = { type };
|
|
226
|
+
const tp = fin(r.target_price);
|
|
227
|
+
if (tp != null && tp > 0)
|
|
228
|
+
out.targetPrice = tp;
|
|
229
|
+
const ta = fin(r.trail_after_r);
|
|
230
|
+
if (ta != null)
|
|
231
|
+
out.trailAfterR = ta;
|
|
232
|
+
const td = fin(r.trail_distance_r);
|
|
233
|
+
if (td != null && td > 0)
|
|
234
|
+
out.trailDistanceR = td;
|
|
235
|
+
if (Array.isArray(r.scale)) {
|
|
236
|
+
const rungs = [];
|
|
237
|
+
for (const s of r.scale) {
|
|
238
|
+
if (!s || typeof s !== 'object')
|
|
239
|
+
continue;
|
|
240
|
+
const o = s;
|
|
241
|
+
const atR = fin(o.at_r);
|
|
242
|
+
const fraction = fin(o.fraction);
|
|
243
|
+
if (atR == null || fraction == null || fraction <= 0 || fraction > 1)
|
|
244
|
+
continue;
|
|
245
|
+
rungs.push({ atR, fraction });
|
|
246
|
+
if (rungs.length >= 6)
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
if (rungs.length > 0)
|
|
250
|
+
out.scale = rungs;
|
|
251
|
+
}
|
|
252
|
+
if (typeof r.note === 'string' && r.note.trim().length > 0) {
|
|
253
|
+
out.note = r.note.slice(0, 200);
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
// ---- record_position_reviews (Position Decision Journal) ----
|
|
258
|
+
/** Verdicts the agent can file at heartbeat for an open position. `trim` is
|
|
259
|
+
* intentionally absent until `partial_close` exists as a real action — see
|
|
260
|
+
* POSITION_DECISION_JOURNAL_PLAN.md §3 redirect #4. */
|
|
261
|
+
export const REVIEW_VERDICTS = ['hold', 'add_on', 'close_recommended'];
|
|
262
|
+
export const THESIS_STATUSES = ['intact', 'weakening', 'invalidated', 'evolving'];
|
|
263
|
+
/** Validate a `record_position_reviews` call. Per-review rules from
|
|
264
|
+
* POSITION_REVIEW_PLAN.md §7.2 plus a top-level superset rule when the caller
|
|
265
|
+
* passes the open-positions snapshot.
|
|
266
|
+
*
|
|
267
|
+
* Returns either {ok:true} (proceed) or {ok:false, error} (reject the whole
|
|
268
|
+
* call — partial-success would corrupt the audit trail).
|
|
269
|
+
*/
|
|
270
|
+
export function validatePositionReviews(args, ctx) {
|
|
271
|
+
if (!Array.isArray(args.reviews)) {
|
|
272
|
+
return { ok: false, error: 'reviews must be an array' };
|
|
273
|
+
}
|
|
274
|
+
if (args.reviews.length === 0) {
|
|
275
|
+
return { ok: false, error: 'reviews must contain at least one entry' };
|
|
276
|
+
}
|
|
277
|
+
const seen = new Set();
|
|
278
|
+
for (let i = 0; i < args.reviews.length; i++) {
|
|
279
|
+
const r = args.reviews[i];
|
|
280
|
+
const where = `reviews[${i}]`;
|
|
281
|
+
if (!nonEmptyString(r.symbol)) {
|
|
282
|
+
return { ok: false, error: `${where}: symbol required` };
|
|
283
|
+
}
|
|
284
|
+
const symbol = r.symbol;
|
|
285
|
+
if (seen.has(symbol)) {
|
|
286
|
+
return { ok: false, error: `${where}: duplicate symbol '${symbol}' — one review per position per call` };
|
|
287
|
+
}
|
|
288
|
+
seen.add(symbol);
|
|
289
|
+
if (typeof r.position_open_at !== 'number' || !Number.isFinite(r.position_open_at) || r.position_open_at <= 0) {
|
|
290
|
+
return { ok: false, error: `${where}: position_open_at required (positive epoch ms)` };
|
|
291
|
+
}
|
|
292
|
+
if (!REVIEW_VERDICTS.includes(r.verdict)) {
|
|
293
|
+
return {
|
|
294
|
+
ok: false,
|
|
295
|
+
error: `${where}: verdict must be one of: ${REVIEW_VERDICTS.join(', ')}`,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const verdict = r.verdict;
|
|
299
|
+
if (!THESIS_STATUSES.includes(r.thesis_status)) {
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
error: `${where}: thesis_status must be one of: ${THESIS_STATUSES.join(', ')}`,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
const thesisStatus = r.thesis_status;
|
|
306
|
+
// Rule 1 — verdict='hold' AND thesis_status='invalidated' is a logical contradiction.
|
|
307
|
+
if (verdict === 'hold' && thesisStatus === 'invalidated') {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
error: `${where}: verdict='hold' contradicts thesis_status='invalidated'. If thesis is invalidated, file verdict='close_recommended' or call close_position.`,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Numeric required fields.
|
|
314
|
+
if (!isFiniteNumber(r.entry_score))
|
|
315
|
+
return { ok: false, error: `${where}: entry_score required (number)` };
|
|
316
|
+
if (!isFiniteNumber(r.current_score))
|
|
317
|
+
return { ok: false, error: `${where}: current_score required (number)` };
|
|
318
|
+
if (!isFiniteNumber(r.score_delta))
|
|
319
|
+
return { ok: false, error: `${where}: score_delta required (number)` };
|
|
320
|
+
// Rule 3 — internal consistency between scores and delta (float tolerance).
|
|
321
|
+
const computedDelta = r.current_score - r.entry_score;
|
|
322
|
+
if (Math.abs(computedDelta - r.score_delta) > 0.01) {
|
|
323
|
+
return {
|
|
324
|
+
ok: false,
|
|
325
|
+
error: `${where}: score_delta=${r.score_delta} inconsistent with current_score=${r.current_score} - entry_score=${r.entry_score} = ${computedDelta.toFixed(3)}`,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (!isFiniteNumber(r.regime_confidence) || r.regime_confidence < 0 || r.regime_confidence > 1) {
|
|
329
|
+
return { ok: false, error: `${where}: regime_confidence required in [0, 1]` };
|
|
330
|
+
}
|
|
331
|
+
if (!isFiniteNumber(r.r_multiple))
|
|
332
|
+
return { ok: false, error: `${where}: r_multiple required` };
|
|
333
|
+
if (!isFiniteNumber(r.distance_to_stop_bps))
|
|
334
|
+
return { ok: false, error: `${where}: distance_to_stop_bps required` };
|
|
335
|
+
if (!isFiniteNumber(r.time_in_position_minutes) || r.time_in_position_minutes < 0) {
|
|
336
|
+
return { ok: false, error: `${where}: time_in_position_minutes required (non-negative)` };
|
|
337
|
+
}
|
|
338
|
+
if (!isFiniteNumber(r.mfe_r))
|
|
339
|
+
return { ok: false, error: `${where}: mfe_r required` };
|
|
340
|
+
if (!isFiniteNumber(r.give_back_pct))
|
|
341
|
+
return { ok: false, error: `${where}: give_back_pct required` };
|
|
342
|
+
// Rule 5 — r_multiple cannot exceed mfe_r (mfe_r is by definition the max
|
|
343
|
+
// favourable R seen since entry).
|
|
344
|
+
if (r.r_multiple > r.mfe_r + 0.01) {
|
|
345
|
+
return {
|
|
346
|
+
ok: false,
|
|
347
|
+
error: `${where}: r_multiple=${r.r_multiple} > mfe_r=${r.mfe_r} is impossible (MFE is the running max).`,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (!nonEmptyString(r.regime_entry))
|
|
351
|
+
return { ok: false, error: `${where}: regime_entry required` };
|
|
352
|
+
if (!nonEmptyString(r.regime_current))
|
|
353
|
+
return { ok: false, error: `${where}: regime_current required` };
|
|
354
|
+
if (!r.confluence_components || typeof r.confluence_components !== 'object' || Array.isArray(r.confluence_components)) {
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
error: `${where}: confluence_components required (named-boolean object). ` +
|
|
358
|
+
`Example: {regime: true, structure: true, momentum: false, flow: true}. ` +
|
|
359
|
+
`One key per scorecard gate firing or not firing NOW.`,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
// Rule 4 — what_changed minimum length. Fuzzy-match-against-prior-reviews
|
|
363
|
+
// is a server-side concern (the validator doesn't have history); enforce
|
|
364
|
+
// length only here.
|
|
365
|
+
if (!nonEmptyString(r.what_changed) || r.what_changed.length < 20) {
|
|
366
|
+
return { ok: false, error: `${where}: what_changed required (≥20 chars describing what shifted since last review)` };
|
|
367
|
+
}
|
|
368
|
+
if (!nonEmptyString(r.invalidation_trigger)) {
|
|
369
|
+
return { ok: false, error: `${where}: invalidation_trigger required (the price/event/time that flips this to CLOSE)` };
|
|
370
|
+
}
|
|
371
|
+
// Rule 2 — verdict='add_on' requires +0.5R AND +1.5 score delta vs entry.
|
|
372
|
+
// Already-prose rule from SKILL.md v2.9; now mechanical.
|
|
373
|
+
if (verdict === 'add_on') {
|
|
374
|
+
const r1 = r.r_multiple;
|
|
375
|
+
const dScore = r.current_score - r.entry_score;
|
|
376
|
+
if (r1 < 0.5 || dScore < 1.5) {
|
|
377
|
+
return {
|
|
378
|
+
ok: false,
|
|
379
|
+
error: `${where}: verdict='add_on' requires r_multiple ≥ +0.5 AND score_delta ≥ +1.5. Got r_multiple=${r1.toFixed(2)}, score_delta=${dScore.toFixed(2)}. Either HOLD this position or wait for confirmation.`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Top-level superset rule — every open position must have a review when
|
|
385
|
+
// gate-mode is 'enforce'. Soft warning at lower modes happens at the call
|
|
386
|
+
// site, not here.
|
|
387
|
+
//
|
|
388
|
+
// Symbol form caveat: Binance live `getPositions()` returns settle-suffixed
|
|
389
|
+
// unified symbols ('SOL/USDT:USDT'), while the state-store + agent-side
|
|
390
|
+
// reviews use the canonical 'SOL/USDT'. Normalize BOTH sides — the call site
|
|
391
|
+
// normalizes openSymbols defensively too, but doing it here as well makes
|
|
392
|
+
// the validator robust against any caller that forwards raw forms.
|
|
393
|
+
if (ctx?.openSymbols && ctx.gateMode === 'enforce') {
|
|
394
|
+
const reviewedSymbols = new Set();
|
|
395
|
+
for (const r of args.reviews) {
|
|
396
|
+
if (typeof r.symbol === 'string')
|
|
397
|
+
reviewedSymbols.add(normalizeBracketSymbol(r.symbol));
|
|
398
|
+
}
|
|
399
|
+
const missing = [];
|
|
400
|
+
for (const s of ctx.openSymbols) {
|
|
401
|
+
if (!reviewedSymbols.has(normalizeBracketSymbol(s)))
|
|
402
|
+
missing.push(s);
|
|
403
|
+
}
|
|
404
|
+
if (missing.length > 0) {
|
|
405
|
+
return {
|
|
406
|
+
ok: false,
|
|
407
|
+
error: `record_position_reviews missing reviews for open positions: ${missing.join(', ')}. Pass one entry per open position.`,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { ok: true };
|
|
412
|
+
}
|
|
413
|
+
function isFiniteNumber(v) {
|
|
414
|
+
return typeof v === 'number' && Number.isFinite(v);
|
|
415
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
interface AttachBracketsArgs {
|
|
3
|
+
symbol: string;
|
|
4
|
+
stop_price?: number;
|
|
5
|
+
target_price?: number;
|
|
6
|
+
}
|
|
7
|
+
interface AttachBracketsSuccess {
|
|
8
|
+
ok: true;
|
|
9
|
+
symbol: string;
|
|
10
|
+
bracket_id: string;
|
|
11
|
+
entry_side: 'buy' | 'sell';
|
|
12
|
+
stop_price?: number;
|
|
13
|
+
target_price?: number;
|
|
14
|
+
sl_cid?: string;
|
|
15
|
+
tp_cid?: string;
|
|
16
|
+
attach_latency_ms: number;
|
|
17
|
+
cancelled_stale_bracket_orders: number;
|
|
18
|
+
cleared_stale_ledger_row: boolean;
|
|
19
|
+
attempts: number;
|
|
20
|
+
/** True when the call short-circuited because the requested protection
|
|
21
|
+
* is already live on the exchange with matching prices — no new orders
|
|
22
|
+
* were placed. Set by the 2026-05-15 idempotency change so the agent's
|
|
23
|
+
* defensive re-attach loop doesn't tear down and reattach a working
|
|
24
|
+
* bracket pair, which is what produced the audit→reattach spiral.
|
|
25
|
+
*/
|
|
26
|
+
idempotent_no_op?: boolean;
|
|
27
|
+
note?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function attachBracketsTool(args: AttachBracketsArgs, deps: {
|
|
30
|
+
adapter: IExchangeAdapter;
|
|
31
|
+
}): Promise<AttachBracketsSuccess | {
|
|
32
|
+
error: string;
|
|
33
|
+
}>;
|
|
34
|
+
export {};
|