@reefclaw/openclaw-plugin 0.1.5 → 0.1.6
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/ccxt/binance-private.d.ts +21 -0
- package/ccxt/binance-private.js +132 -22
- package/config/plugin-config-io.d.ts +6 -0
- package/config/tool-gate.js +3 -0
- package/exchange-adapter.d.ts +16 -0
- package/index.js +551 -54
- package/lifecycle/trading-operation-lock.d.ts +17 -0
- package/lifecycle/trading-operation-lock.js +14 -0
- package/live/bracket-id.d.ts +2 -3
- package/live/bracket-id.js +22 -9
- package/live/live-adapter.d.ts +24 -1
- package/live/live-adapter.js +114 -2
- package/live/local-signal-service.js +11 -6
- package/live/local-strategy-evaluator.js +4 -0
- package/live/proposal-decision-listener.d.ts +6 -0
- package/live/proposal-decision-listener.js +4 -0
- package/live/stop-watcher.d.ts +27 -1
- package/live/stop-watcher.js +59 -2
- package/onboarding/runtime.d.ts +13 -0
- package/onboarding/runtime.js +22 -2
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/portfolio/wave9-admission.d.ts +67 -0
- package/portfolio/wave9-admission.js +262 -0
- package/portfolio/wave9-policy.d.ts +36 -0
- package/portfolio/wave9-policy.js +183 -0
- package/signals/conditions/registry.js +50 -0
- package/simulator/exchange-simulator.js +5 -0
- package/simulator/fill-engine.js +5 -1
- package/simulator/types.d.ts +6 -1
- package/strategy/evaluator.d.ts +3 -0
- package/strategy/evaluator.js +5 -0
- package/tools/assessment-validation.d.ts +2 -0
- package/tools/attach-brackets.d.ts +7 -2
- package/tools/attach-brackets.js +36 -0
- package/tools/cancel-all-orders.d.ts +2 -0
- package/tools/cancel-all-orders.js +4 -1
- package/tools/cancel-order.d.ts +4 -0
- package/tools/cancel-order.js +49 -3
- package/tools/close-position.d.ts +23 -0
- package/tools/close-position.js +286 -13
- package/tools/create-order.d.ts +28 -0
- package/tools/create-order.js +1364 -192
- package/tools/get-analytics.js +2 -2
- package/tools/get-basis.js +2 -2
- package/tools/get-cascade-risk.js +2 -2
- package/tools/get-crypto-metrics.js +14 -4
- package/tools/get-cvd.js +2 -2
- package/tools/get-divergences.js +2 -2
- package/tools/get-funding-context.js +2 -2
- package/tools/get-liquidation-levels.js +2 -2
- package/tools/get-liquidation-pulse.js +2 -2
- package/tools/get-pattern-scan.js +2 -2
- package/tools/get-regime.js +2 -2
- package/tools/get-resting-liquidity.js +2 -2
- package/tools/get-risk-scenario.js +2 -2
- package/tools/get-session-review.js +2 -2
- package/tools/get-setup-detail.js +9 -1
- package/tools/get-signals.js +2 -2
- package/tools/get-sizing.js +2 -2
- package/tools/get-trade-feedback.js +2 -2
- package/tools/get-trade-flow.js +2 -2
- package/tools/get-volume-profile.js +2 -2
- package/tools/get-wave9-status.d.ts +127 -0
- package/tools/get-wave9-status.js +796 -0
- package/tools/intel-api.d.ts +20 -0
- package/tools/intel-api.js +67 -0
- package/tools/intel-cache.d.ts +1 -1
- package/tools/intel-cache.js +20 -5
- package/tools/list-strategies.d.ts +11 -1
- package/tools/list-strategies.js +17 -0
- package/tools/modify-stop.d.ts +4 -0
- package/tools/modify-stop.js +63 -24
- package/tools/modify-target.d.ts +4 -0
- package/tools/modify-target.js +62 -23
- package/tools/scan-pairs.js +19 -8
- package/tools/toggle-strategy.js +7 -0
- package/types.d.ts +5 -0
- package/venues/hyperliquid/hl-balance.d.ts +116 -0
- package/venues/hyperliquid/hl-balance.js +145 -0
- package/venues/hyperliquid/hl-brackets.d.ts +102 -0
- package/venues/hyperliquid/hl-brackets.js +172 -0
- package/venues/hyperliquid/hl-cloid.d.ts +22 -0
- package/venues/hyperliquid/hl-cloid.js +82 -0
- package/venues/hyperliquid/hl-info-cache.d.ts +46 -0
- package/venues/hyperliquid/hl-info-cache.js +125 -0
- package/venues/hyperliquid/hl-live-adapter.d.ts +88 -0
- package/venues/hyperliquid/hl-live-adapter.js +353 -0
- package/venues/hyperliquid/hl-precision.d.ts +61 -0
- package/venues/hyperliquid/hl-precision.js +176 -0
- package/venues/hyperliquid/hl-private.d.ts +88 -0
- package/venues/hyperliquid/hl-private.js +357 -0
- package/venues/hyperliquid/hl-public.d.ts +31 -4
- package/venues/hyperliquid/hl-public.js +155 -11
- package/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
- package/venues/hyperliquid/hl-rate-gate.js +220 -0
- package/venues/hyperliquid/hl-user-stream.d.ts +90 -0
- package/venues/hyperliquid/hl-user-stream.js +220 -0
- package/venues/registry.d.ts +23 -9
- package/venues/registry.js +12 -13
- package/venues/symbols.d.ts +43 -0
- package/venues/symbols.js +107 -0
- package/wave9/live-account-capture.d.ts +67 -0
- package/wave9/live-account-capture.js +435 -0
- package/wave9/live-autonomous-protection.d.ts +39 -0
- package/wave9/live-autonomous-protection.js +112 -0
- package/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
- package/wave9/live-durable-reconciliation-scheduler.js +115 -0
- package/wave9/live-execution-ledger.d.ts +107 -0
- package/wave9/live-execution-ledger.js +498 -0
- package/wave9/live-position-confirmation.d.ts +18 -0
- package/wave9/live-position-confirmation.js +111 -0
- package/wave9/live-residual-protection.d.ts +18 -0
- package/wave9/live-residual-protection.js +250 -0
- package/wave9/live-startup-reconciliation.d.ts +38 -0
- package/wave9/live-startup-reconciliation.js +454 -0
- package/wave9/live-symbol-ownership.d.ts +20 -0
- package/wave9/live-symbol-ownership.js +132 -0
- package/wave9/paper-admission-guard.d.ts +199 -0
- package/wave9/paper-admission-guard.js +650 -0
- package/wave9/usdm-evidence-provider.d.ts +42 -0
- package/wave9/usdm-evidence-provider.js +133 -0
package/tools/intel-api.d.ts
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
|
+
import { type VenueId } from '../venues/symbols.js';
|
|
1
2
|
export interface IntelApiDeps {
|
|
2
3
|
connectionToken: string;
|
|
3
4
|
intelligenceUrl: string;
|
|
5
|
+
/** Trading venue of this box (HYPERLIQUID_INTEGRATION_PLAN §5.3). Absent =
|
|
6
|
+
* binance, so every pre-venue caller is byte-identical. */
|
|
7
|
+
venue?: VenueId;
|
|
4
8
|
}
|
|
9
|
+
/** Agent-facing symbol → the intel DB symbol for this deps' venue.
|
|
10
|
+
* binance/absent: 'BTC/USDT' → 'BTCUSDT'; slashless input passes through
|
|
11
|
+
* verbatim (today's working raw inputs stay byte-identical).
|
|
12
|
+
* hyperliquid: 'BTC/USDC' → 'HL_BTC' (case preserved — 'kPEPE' is real,
|
|
13
|
+
* 'KPEPE' is not); 'HL_X' passes through; concatenated 'BTCUSDC' → 'HL_BTC'. */
|
|
14
|
+
export declare function resolveIntelSymbol(deps: IntelApiDeps, symbol: string): string;
|
|
15
|
+
/** Intel DB symbol → the symbol the agent should see and trade.
|
|
16
|
+
* binance: verbatim ('BTCUSDT' stays 'BTCUSDT' — the historical shape the
|
|
17
|
+
* agent already translates). hyperliquid: 'HL_BTC' → 'BTC/USDC'. */
|
|
18
|
+
export declare function presentIntelSymbol(deps: IntelApiDeps, intelSymbol: string): string;
|
|
19
|
+
/** Does this intel symbol belong to this deps' venue? Venue-scopes the scan
|
|
20
|
+
* universe: an HL box must never rank Binance USDT candidates it cannot
|
|
21
|
+
* trade (2026-07-13 wisekid: every setup died at the order-path price
|
|
22
|
+
* fetch), and a Binance box must never rank HL_ rows once intel serves
|
|
23
|
+
* them (INTEL_HL_FACTS). */
|
|
24
|
+
export declare function intelSymbolOnVenue(deps: IntelApiDeps, intelSymbol: string): boolean;
|
|
5
25
|
export interface FetchOptions {
|
|
6
26
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
7
27
|
body?: unknown;
|
package/tools/intel-api.js
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
// Shared helper for Intelligence API calls.
|
|
2
2
|
// All intelligence tools use Bearer token auth against intel.reefclaw.com.
|
|
3
|
+
import { HL_INTEL_PREFIX, fromIntelSymbol, toIntelSymbol } from '../venues/symbols.js';
|
|
4
|
+
// ─── Venue-aware intel-symbol mapping (plan §5.3: the agent keeps using
|
|
5
|
+
// canonical symbols like BTC/USDC; these helpers translate at the intel
|
|
6
|
+
// boundary). All three FAIL OPEN — unmappable input passes through
|
|
7
|
+
// verbatim so intel answers "no data for <echo>", which is honest and
|
|
8
|
+
// debuggable; never throw here. ─────────────────────────────────────────
|
|
9
|
+
/** Strip a ccxt settle suffix ('BTC/USDC:USDC' → 'BTC/USDC'). */
|
|
10
|
+
function stripSettle(s) {
|
|
11
|
+
return s.replace(/:[A-Za-z]+$/, '');
|
|
12
|
+
}
|
|
13
|
+
/** Agent-facing symbol → the intel DB symbol for this deps' venue.
|
|
14
|
+
* binance/absent: 'BTC/USDT' → 'BTCUSDT'; slashless input passes through
|
|
15
|
+
* verbatim (today's working raw inputs stay byte-identical).
|
|
16
|
+
* hyperliquid: 'BTC/USDC' → 'HL_BTC' (case preserved — 'kPEPE' is real,
|
|
17
|
+
* 'KPEPE' is not); 'HL_X' passes through; concatenated 'BTCUSDC' → 'HL_BTC'. */
|
|
18
|
+
export function resolveIntelSymbol(deps, symbol) {
|
|
19
|
+
const venue = deps.venue ?? 'binance';
|
|
20
|
+
const s = stripSettle(String(symbol ?? '').trim());
|
|
21
|
+
if (venue === 'hyperliquid') {
|
|
22
|
+
if (s.startsWith(HL_INTEL_PREFIX))
|
|
23
|
+
return s;
|
|
24
|
+
if (s.includes('/')) {
|
|
25
|
+
try {
|
|
26
|
+
return toIntelSymbol('hyperliquid', s);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return s;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (s.length > 4 && s.endsWith('USDC'))
|
|
33
|
+
return `${HL_INTEL_PREFIX}${s.slice(0, -4)}`;
|
|
34
|
+
return s;
|
|
35
|
+
}
|
|
36
|
+
if (s.includes('/')) {
|
|
37
|
+
try {
|
|
38
|
+
return toIntelSymbol('binance', s);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return s;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
/** Intel DB symbol → the symbol the agent should see and trade.
|
|
47
|
+
* binance: verbatim ('BTCUSDT' stays 'BTCUSDT' — the historical shape the
|
|
48
|
+
* agent already translates). hyperliquid: 'HL_BTC' → 'BTC/USDC'. */
|
|
49
|
+
export function presentIntelSymbol(deps, intelSymbol) {
|
|
50
|
+
if ((deps.venue ?? 'binance') !== 'hyperliquid')
|
|
51
|
+
return intelSymbol;
|
|
52
|
+
if (!intelSymbol.startsWith(HL_INTEL_PREFIX))
|
|
53
|
+
return intelSymbol;
|
|
54
|
+
try {
|
|
55
|
+
return fromIntelSymbol(intelSymbol).canonical;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return intelSymbol;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Does this intel symbol belong to this deps' venue? Venue-scopes the scan
|
|
62
|
+
* universe: an HL box must never rank Binance USDT candidates it cannot
|
|
63
|
+
* trade (2026-07-13 wisekid: every setup died at the order-path price
|
|
64
|
+
* fetch), and a Binance box must never rank HL_ rows once intel serves
|
|
65
|
+
* them (INTEL_HL_FACTS). */
|
|
66
|
+
export function intelSymbolOnVenue(deps, intelSymbol) {
|
|
67
|
+
const isHl = intelSymbol.startsWith(HL_INTEL_PREFIX);
|
|
68
|
+
return (deps.venue ?? 'binance') === 'hyperliquid' ? isHl : !isHl;
|
|
69
|
+
}
|
|
3
70
|
/** Encode a value for safe use in a URL path segment */
|
|
4
71
|
export const enc = encodeURIComponent;
|
|
5
72
|
export async function fetchIntelApi(path, deps, options) {
|
package/tools/intel-cache.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IntelApiDeps } from './intel-api.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type SymbolFact, type StrategyConfig, type StrategyRow } from '../strategy/evaluator.js';
|
|
3
3
|
export declare function getAllFactsCached(deps: IntelApiDeps): Promise<SymbolFact[] | {
|
|
4
4
|
error: string;
|
|
5
5
|
}>;
|
package/tools/intel-cache.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
// per-symbol cache
|
|
19
19
|
// getStrategiesCached(deps) — strategies cache
|
|
20
20
|
// __testing__.resetCaches() — clears all caches between tests
|
|
21
|
-
import { fetchIntelApi, enc } from './intel-api.js';
|
|
21
|
+
import { fetchIntelApi, enc, resolveIntelSymbol } from './intel-api.js';
|
|
22
|
+
import { isDeclarativeStrategyConfig, } from '../strategy/evaluator.js';
|
|
22
23
|
// facts refresh every 5 min server-side; 30s client cache is safe
|
|
23
24
|
const FACTS_TTL_MS = 30_000;
|
|
24
25
|
// strategies are slow-changing user state
|
|
@@ -40,7 +41,14 @@ export async function getAllFactsCached(deps) {
|
|
|
40
41
|
const now = Date.now();
|
|
41
42
|
if (allFactsCache && now < allFactsCache.expiry)
|
|
42
43
|
return allFactsCache.value;
|
|
43
|
-
|
|
44
|
+
// HL_ facts are opt-in server-side (?venue=hyperliquid) so venue-blind
|
|
45
|
+
// plugin builds can never receive them; binance sends the historical bare
|
|
46
|
+
// path — byte-identical. One process = one venue, so the module-global
|
|
47
|
+
// cache needs no venue key.
|
|
48
|
+
const factsPath = (deps.venue ?? 'binance') === 'hyperliquid'
|
|
49
|
+
? '/api/facts?venue=hyperliquid'
|
|
50
|
+
: '/api/facts';
|
|
51
|
+
const resp = await fetchIntelApi(factsPath, deps, { timeoutMs: 30_000 });
|
|
44
52
|
if (!resp || typeof resp !== 'object') {
|
|
45
53
|
return { error: 'Intelligence /api/facts returned no response.' };
|
|
46
54
|
}
|
|
@@ -83,8 +91,14 @@ export async function getFactBySymbolCached(rawSymbol, deps) {
|
|
|
83
91
|
}
|
|
84
92
|
// 3. Fall back to the single-symbol endpoint (symbol not in default scan
|
|
85
93
|
// set, or both caches cold). Populate the per-symbol cache so a repeat
|
|
86
|
-
// call in the same heartbeat is free.
|
|
87
|
-
|
|
94
|
+
// call in the same heartbeat is free. The FETCH form must preserve the
|
|
95
|
+
// venue namespace and case ('HL_kPEPE' — the normalized cache key would
|
|
96
|
+
// mangle it to 'HLKPEPE'); binance keeps fetching the normalized form
|
|
97
|
+
// byte-identically.
|
|
98
|
+
const fetchSym = (deps.venue ?? 'binance') === 'hyperliquid'
|
|
99
|
+
? resolveIntelSymbol(deps, rawSymbol)
|
|
100
|
+
: symbol;
|
|
101
|
+
const resp = await fetchIntelApi(`/api/facts/${enc(fetchSym)}`, deps, { timeoutMs: 30_000 });
|
|
88
102
|
if (!resp || typeof resp !== 'object') {
|
|
89
103
|
return { error: `Intelligence /api/facts/${symbol} returned no response.` };
|
|
90
104
|
}
|
|
@@ -113,7 +127,8 @@ export async function getStrategyRowsCached(deps) {
|
|
|
113
127
|
}
|
|
114
128
|
if ('error' in resp)
|
|
115
129
|
return { error: resp.error };
|
|
116
|
-
const rows = (resp.strategies ?? [])
|
|
130
|
+
const rows = (resp.strategies ?? [])
|
|
131
|
+
.filter((row) => row?.config && isDeclarativeStrategyConfig(row.config));
|
|
117
132
|
stratRowCache = { value: rows, expiry: now + STRAT_TTL_MS };
|
|
118
133
|
return rows;
|
|
119
134
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { IntelApiDeps } from './intel-api.js';
|
|
2
2
|
export interface ListStrategiesArgs {
|
|
3
|
-
active_only
|
|
3
|
+
active_only: boolean;
|
|
4
4
|
}
|
|
5
|
+
/**
|
|
6
|
+
* Canonicalize model-produced arguments before OpenClaw validates them.
|
|
7
|
+
*
|
|
8
|
+
* OpenClaw's production validator coerces a JSON null to boolean false. That
|
|
9
|
+
* would turn a malformed/omitted active-only request into an all-strategies
|
|
10
|
+
* request. Only a genuine boolean false therefore opts into inactive rows;
|
|
11
|
+
* every missing, null, string, array, or otherwise malformed input fails safe
|
|
12
|
+
* to the active strategies requested by the operator-status workflow.
|
|
13
|
+
*/
|
|
14
|
+
export declare function prepareListStrategiesArgs(raw: unknown): ListStrategiesArgs;
|
|
5
15
|
export declare function listStrategiesTool(args: ListStrategiesArgs, deps: IntelApiDeps): Promise<Record<string, unknown> | {
|
|
6
16
|
error: string;
|
|
7
17
|
}>;
|
package/tools/list-strategies.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
// Tool: list_strategies — list all strategies in the Strategy Box.
|
|
2
2
|
import { fetchIntelApi } from './intel-api.js';
|
|
3
|
+
/**
|
|
4
|
+
* Canonicalize model-produced arguments before OpenClaw validates them.
|
|
5
|
+
*
|
|
6
|
+
* OpenClaw's production validator coerces a JSON null to boolean false. That
|
|
7
|
+
* would turn a malformed/omitted active-only request into an all-strategies
|
|
8
|
+
* request. Only a genuine boolean false therefore opts into inactive rows;
|
|
9
|
+
* every missing, null, string, array, or otherwise malformed input fails safe
|
|
10
|
+
* to the active strategies requested by the operator-status workflow.
|
|
11
|
+
*/
|
|
12
|
+
export function prepareListStrategiesArgs(raw) {
|
|
13
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
14
|
+
return { active_only: true };
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
active_only: raw.active_only === false ? false : true,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
3
20
|
export async function listStrategiesTool(args, deps) {
|
|
4
21
|
const query = args.active_only ? '?active=true' : '';
|
|
5
22
|
return fetchIntelApi(`/api/strategies${query}`, deps);
|
package/tools/modify-stop.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
3
|
+
import type { Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
|
|
2
4
|
interface ModifyStopArgs {
|
|
3
5
|
symbol: string;
|
|
4
6
|
new_stop_price: number;
|
|
@@ -11,6 +13,8 @@ interface ModifyStopResult {
|
|
|
11
13
|
}
|
|
12
14
|
export declare function modifyStopTool(args: ModifyStopArgs, deps: {
|
|
13
15
|
adapter: IExchangeAdapter;
|
|
16
|
+
operationLock?: TradingOperationLock;
|
|
17
|
+
checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
|
|
14
18
|
}): Promise<ModifyStopResult | {
|
|
15
19
|
error: string;
|
|
16
20
|
}>;
|
package/tools/modify-stop.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Live mode without brackets enabled: error asking operator to enable.
|
|
8
8
|
import { validateStopDirection } from '../live/bracket-params.js';
|
|
9
9
|
import { formatError } from '../logger.js';
|
|
10
|
+
import { isWave9ManagedPosition } from '../wave9/paper-admission-guard.js';
|
|
10
11
|
export async function modifyStopTool(args, deps) {
|
|
11
12
|
if (!args.symbol)
|
|
12
13
|
return { error: 'symbol is required.' };
|
|
@@ -16,33 +17,68 @@ export async function modifyStopTool(args, deps) {
|
|
|
16
17
|
if (!deps.adapter.isLive) {
|
|
17
18
|
return modifyStopPaper(deps.adapter, args);
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
const ledger = deps.adapter.getBracketLedger?.();
|
|
21
|
-
if (!mgr || !ledger) {
|
|
22
|
-
return { error: 'Bracket orders are not enabled. Set brackets.mode in plugin-config.json to "observe" or "enforce".' };
|
|
23
|
-
}
|
|
24
|
-
const entry = ledger.getBySymbol(args.symbol);
|
|
25
|
-
if (!entry) {
|
|
26
|
-
return { error: `No active bracket for ${args.symbol}.` };
|
|
27
|
-
}
|
|
28
|
-
// Direction check — catch flipped sign before burning a cancel+resubmit cycle
|
|
29
|
-
const refPrice = entry.stopPrice && entry.targetPrice
|
|
30
|
-
? (entry.stopPrice + entry.targetPrice) / 2
|
|
31
|
-
: await deps.adapter.getLastPrice(args.symbol);
|
|
32
|
-
if (refPrice != null && refPrice > 0) {
|
|
33
|
-
const msg = validateStopDirection(entry.entrySide, refPrice, args.new_stop_price);
|
|
34
|
-
if (msg)
|
|
35
|
-
return { error: msg };
|
|
36
|
-
}
|
|
37
|
-
try {
|
|
38
|
-
await mgr.modifyStop(args.symbol, args.new_stop_price);
|
|
20
|
+
if (deps.checkWave9LiveSymbolOwnership && !deps.operationLock) {
|
|
39
21
|
return {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
old_stop_price: entry.stopPrice ?? null,
|
|
43
|
-
new_stop_price: args.new_stop_price,
|
|
22
|
+
error: 'Live modify_stop refused: the shared trading operation lock is required ' +
|
|
23
|
+
'while Wave 9 LIVE ownership checks are enabled.',
|
|
44
24
|
};
|
|
45
25
|
}
|
|
26
|
+
const modifyLive = async () => {
|
|
27
|
+
if (deps.checkWave9LiveSymbolOwnership) {
|
|
28
|
+
let ownership;
|
|
29
|
+
try {
|
|
30
|
+
ownership = await deps.checkWave9LiveSymbolOwnership(args.symbol);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
return {
|
|
34
|
+
error: `modify_stop refused at mutation boundary: Wave 9 LIVE ownership check failed ` +
|
|
35
|
+
`for ${args.symbol} (${formatError(err)}).`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
|
|
39
|
+
return {
|
|
40
|
+
error: `modify_stop refused at mutation boundary: ${args.symbol} has ` +
|
|
41
|
+
`${ownership.status} Wave 9 LIVE ownership (${ownership.reason}). ` +
|
|
42
|
+
'Wave 9 uses its frozen initial 3x ATR emergency stop.',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const mgr = deps.adapter.getBracketManager?.();
|
|
47
|
+
const ledger = deps.adapter.getBracketLedger?.();
|
|
48
|
+
if (!mgr || !ledger) {
|
|
49
|
+
return { error: 'Bracket orders are not enabled. Set brackets.mode in plugin-config.json to "observe" or "enforce".' };
|
|
50
|
+
}
|
|
51
|
+
const entry = ledger.getBySymbol(args.symbol);
|
|
52
|
+
if (!entry) {
|
|
53
|
+
return { error: `No active bracket for ${args.symbol}.` };
|
|
54
|
+
}
|
|
55
|
+
// Direction check — catch flipped sign before burning a cancel+resubmit cycle
|
|
56
|
+
const refPrice = entry.stopPrice && entry.targetPrice
|
|
57
|
+
? (entry.stopPrice + entry.targetPrice) / 2
|
|
58
|
+
: await deps.adapter.getLastPrice(args.symbol);
|
|
59
|
+
if (refPrice != null && refPrice > 0) {
|
|
60
|
+
const msg = validateStopDirection(entry.entrySide, refPrice, args.new_stop_price);
|
|
61
|
+
if (msg)
|
|
62
|
+
return { error: msg };
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await mgr.modifyStop(args.symbol, args.new_stop_price);
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
symbol: args.symbol,
|
|
69
|
+
old_stop_price: entry.stopPrice ?? null,
|
|
70
|
+
new_stop_price: args.new_stop_price,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
return { error: formatError(err) };
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
return deps.operationLock
|
|
79
|
+
? await deps.operationLock.withAccountLock(`generic-modify-stop-final:${args.symbol}`, modifyLive)
|
|
80
|
+
: await modifyLive();
|
|
81
|
+
}
|
|
46
82
|
catch (err) {
|
|
47
83
|
return { error: formatError(err) };
|
|
48
84
|
}
|
|
@@ -56,6 +92,9 @@ function modifyStopPaper(adapter, args) {
|
|
|
56
92
|
if (!pos) {
|
|
57
93
|
return { error: `No open position for ${args.symbol}.` };
|
|
58
94
|
}
|
|
95
|
+
if (isWave9ManagedPosition(pos)) {
|
|
96
|
+
return { error: 'modify_stop refused: Wave 9 uses its frozen initial 3x ATR emergency stop.' };
|
|
97
|
+
}
|
|
59
98
|
// Validate against the current mark (permits breakeven / trailing — the stop
|
|
60
99
|
// just has to stay on the losing side of where price is now).
|
|
61
100
|
const refPrice = pos.markPrice;
|
package/tools/modify-target.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
3
|
+
import type { Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
|
|
2
4
|
interface ModifyTargetArgs {
|
|
3
5
|
symbol: string;
|
|
4
6
|
new_target_price: number;
|
|
@@ -11,6 +13,8 @@ interface ModifyTargetResult {
|
|
|
11
13
|
}
|
|
12
14
|
export declare function modifyTargetTool(args: ModifyTargetArgs, deps: {
|
|
13
15
|
adapter: IExchangeAdapter;
|
|
16
|
+
operationLock?: TradingOperationLock;
|
|
17
|
+
checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
|
|
14
18
|
}): Promise<ModifyTargetResult | {
|
|
15
19
|
error: string;
|
|
16
20
|
}>;
|
package/tools/modify-target.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// position. Mirror of modify_stop; see that file for rationale.
|
|
3
3
|
import { validateTargetDirection } from '../live/bracket-params.js';
|
|
4
4
|
import { formatError } from '../logger.js';
|
|
5
|
+
import { isWave9ManagedPosition } from '../wave9/paper-admission-guard.js';
|
|
5
6
|
export async function modifyTargetTool(args, deps) {
|
|
6
7
|
if (!args.symbol)
|
|
7
8
|
return { error: 'symbol is required.' };
|
|
@@ -11,32 +12,67 @@ export async function modifyTargetTool(args, deps) {
|
|
|
11
12
|
if (!deps.adapter.isLive) {
|
|
12
13
|
return modifyTargetPaper(deps.adapter, args);
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
const ledger = deps.adapter.getBracketLedger?.();
|
|
16
|
-
if (!mgr || !ledger) {
|
|
17
|
-
return { error: 'Bracket orders are not enabled. Set brackets.mode in plugin-config.json to "observe" or "enforce".' };
|
|
18
|
-
}
|
|
19
|
-
const entry = ledger.getBySymbol(args.symbol);
|
|
20
|
-
if (!entry) {
|
|
21
|
-
return { error: `No active bracket for ${args.symbol}.` };
|
|
22
|
-
}
|
|
23
|
-
const refPrice = entry.stopPrice && entry.targetPrice
|
|
24
|
-
? (entry.stopPrice + entry.targetPrice) / 2
|
|
25
|
-
: await deps.adapter.getLastPrice(args.symbol);
|
|
26
|
-
if (refPrice != null && refPrice > 0) {
|
|
27
|
-
const msg = validateTargetDirection(entry.entrySide, refPrice, args.new_target_price);
|
|
28
|
-
if (msg)
|
|
29
|
-
return { error: msg };
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
await mgr.modifyTarget(args.symbol, args.new_target_price);
|
|
15
|
+
if (deps.checkWave9LiveSymbolOwnership && !deps.operationLock) {
|
|
33
16
|
return {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
old_target_price: entry.targetPrice ?? null,
|
|
37
|
-
new_target_price: args.new_target_price,
|
|
17
|
+
error: 'Live modify_target refused: the shared trading operation lock is required ' +
|
|
18
|
+
'while Wave 9 LIVE ownership checks are enabled.',
|
|
38
19
|
};
|
|
39
20
|
}
|
|
21
|
+
const modifyLive = async () => {
|
|
22
|
+
if (deps.checkWave9LiveSymbolOwnership) {
|
|
23
|
+
let ownership;
|
|
24
|
+
try {
|
|
25
|
+
ownership = await deps.checkWave9LiveSymbolOwnership(args.symbol);
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
return {
|
|
29
|
+
error: `modify_target refused at mutation boundary: Wave 9 LIVE ownership check failed ` +
|
|
30
|
+
`for ${args.symbol} (${formatError(err)}).`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
|
|
34
|
+
return {
|
|
35
|
+
error: `modify_target refused at mutation boundary: ${args.symbol} has ` +
|
|
36
|
+
`${ownership.status} Wave 9 LIVE ownership (${ownership.reason}). ` +
|
|
37
|
+
'Wave 9 is targetless and exits only on stop or completed-daily reversal.',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const mgr = deps.adapter.getBracketManager?.();
|
|
42
|
+
const ledger = deps.adapter.getBracketLedger?.();
|
|
43
|
+
if (!mgr || !ledger) {
|
|
44
|
+
return { error: 'Bracket orders are not enabled. Set brackets.mode in plugin-config.json to "observe" or "enforce".' };
|
|
45
|
+
}
|
|
46
|
+
const entry = ledger.getBySymbol(args.symbol);
|
|
47
|
+
if (!entry) {
|
|
48
|
+
return { error: `No active bracket for ${args.symbol}.` };
|
|
49
|
+
}
|
|
50
|
+
const refPrice = entry.stopPrice && entry.targetPrice
|
|
51
|
+
? (entry.stopPrice + entry.targetPrice) / 2
|
|
52
|
+
: await deps.adapter.getLastPrice(args.symbol);
|
|
53
|
+
if (refPrice != null && refPrice > 0) {
|
|
54
|
+
const msg = validateTargetDirection(entry.entrySide, refPrice, args.new_target_price);
|
|
55
|
+
if (msg)
|
|
56
|
+
return { error: msg };
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
await mgr.modifyTarget(args.symbol, args.new_target_price);
|
|
60
|
+
return {
|
|
61
|
+
ok: true,
|
|
62
|
+
symbol: args.symbol,
|
|
63
|
+
old_target_price: entry.targetPrice ?? null,
|
|
64
|
+
new_target_price: args.new_target_price,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return { error: formatError(err) };
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
return deps.operationLock
|
|
73
|
+
? await deps.operationLock.withAccountLock(`generic-modify-target-final:${args.symbol}`, modifyLive)
|
|
74
|
+
: await modifyLive();
|
|
75
|
+
}
|
|
40
76
|
catch (err) {
|
|
41
77
|
return { error: formatError(err) };
|
|
42
78
|
}
|
|
@@ -48,6 +84,9 @@ function modifyTargetPaper(adapter, args) {
|
|
|
48
84
|
if (!pos) {
|
|
49
85
|
return { error: `No open position for ${args.symbol}.` };
|
|
50
86
|
}
|
|
87
|
+
if (isWave9ManagedPosition(pos)) {
|
|
88
|
+
return { error: 'modify_target refused: Wave 9 is targetless and exits only on stop or completed-daily reversal.' };
|
|
89
|
+
}
|
|
51
90
|
const refPrice = pos.markPrice;
|
|
52
91
|
const entrySide = pos.side === 'long' ? 'buy' : 'sell';
|
|
53
92
|
if (Number.isFinite(refPrice) && refPrice > 0) {
|
package/tools/scan-pairs.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// apply override conditions if the directive allows. Saves the drill-in
|
|
16
16
|
// round-trip on the ~6 macd_oversold_reversal-style candidates per
|
|
17
17
|
// heartbeat the agent was rejecting post-scorecard pre-fix.
|
|
18
|
+
import { intelSymbolOnVenue, presentIntelSymbol, resolveIntelSymbol } from './intel-api.js';
|
|
18
19
|
import { scanAllPairs } from '../strategy/evaluator.js';
|
|
19
20
|
import { getAllFactsCached, getStrategiesCached, __testing__ as cacheTesting } from './intel-cache.js';
|
|
20
21
|
import { normalizeSetupFamily, resolveTriggerFamilies } from '../learning/setup-family.js';
|
|
@@ -132,11 +133,19 @@ export async function scanPairsTool(args, deps, decisionsDeps) {
|
|
|
132
133
|
const factsRes = await getAllFactsCached(deps);
|
|
133
134
|
if ('error' in factsRes)
|
|
134
135
|
return factsRes;
|
|
135
|
-
|
|
136
|
-
//
|
|
136
|
+
// Venue scope: only rank symbols this box can actually trade. Without this
|
|
137
|
+
// an HL box ranks Binance USDT candidates that die at the order-path price
|
|
138
|
+
// fetch (2026-07-13 wisekid), and a Binance box would rank HL_ rows once
|
|
139
|
+
// intel serves them. No-op on binance today (facts carry no HL_ rows yet).
|
|
140
|
+
let facts = factsRes.filter(f => intelSymbolOnVenue(deps, f.symbol));
|
|
141
|
+
// Optional symbols filter — narrows the scan to a watchlist. Accepts both
|
|
142
|
+
// the raw intel form (legacy, case-insensitive) and the agent-facing
|
|
143
|
+
// canonical form ('BTC/USDC' → HL_BTC), which is what scan output shows on
|
|
144
|
+
// a non-binance venue.
|
|
137
145
|
if (Array.isArray(args.symbols) && args.symbols.length > 0) {
|
|
138
146
|
const wl = new Set(args.symbols.map(s => s.toUpperCase()));
|
|
139
|
-
|
|
147
|
+
const wlResolved = new Set(args.symbols.map(s => resolveIntelSymbol(deps, s)));
|
|
148
|
+
facts = facts.filter(f => wl.has(f.symbol.toUpperCase()) || wlResolved.has(f.symbol));
|
|
140
149
|
}
|
|
141
150
|
if (facts.length === 0) {
|
|
142
151
|
return {
|
|
@@ -155,9 +164,9 @@ export async function scanPairsTool(args, deps, decisionsDeps) {
|
|
|
155
164
|
timestamp: new Date().toISOString(),
|
|
156
165
|
pairs_scanned: facts.length,
|
|
157
166
|
setups_found: 0,
|
|
158
|
-
note: 'No active strategies.
|
|
167
|
+
note: 'No active strategies — the agent scans every heartbeat but can NEVER enter a trade until one is active. If you have not already done so this session, tell the operator (chat) and offer next steps: list_strategies to see the available templates, toggle_strategy to activate one, or save_strategy to build a custom one.',
|
|
159
168
|
rankings: [],
|
|
160
|
-
no_setup: facts.map(f => f.symbol),
|
|
169
|
+
no_setup: facts.map(f => presentIntelSymbol(deps, f.symbol)),
|
|
161
170
|
};
|
|
162
171
|
}
|
|
163
172
|
// 3. Evaluate all strategies against all facts
|
|
@@ -174,7 +183,9 @@ export async function scanPairsTool(args, deps, decisionsDeps) {
|
|
|
174
183
|
for (const r of results) {
|
|
175
184
|
const matches = entryLearnings.filter(l => learningMatches(l, r.strategy, r.regime));
|
|
176
185
|
const out = {
|
|
177
|
-
|
|
186
|
+
// Agent-facing form: on hyperliquid the agent must see the symbol it
|
|
187
|
+
// can hand straight to create_order ('BTC/USDC'), never 'HL_BTC'.
|
|
188
|
+
symbol: presentIntelSymbol(deps, r.symbol),
|
|
178
189
|
score: r.score,
|
|
179
190
|
regime: r.regime,
|
|
180
191
|
strategy: r.strategy,
|
|
@@ -198,8 +209,8 @@ export async function scanPairsTool(args, deps, decisionsDeps) {
|
|
|
198
209
|
// 6. Build concise response
|
|
199
210
|
const setupSymbols = new Set(results.map(r => r.symbol));
|
|
200
211
|
const noSetup = facts
|
|
201
|
-
.
|
|
202
|
-
.
|
|
212
|
+
.filter(f => !setupSymbols.has(f.symbol))
|
|
213
|
+
.map(f => presentIntelSymbol(deps, f.symbol));
|
|
203
214
|
return {
|
|
204
215
|
timestamp: new Date().toISOString(),
|
|
205
216
|
pairs_scanned: facts.length,
|
package/tools/toggle-strategy.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// Tool: toggle_strategy — activate or deactivate a strategy.
|
|
2
2
|
import { fetchIntelApi } from './intel-api.js';
|
|
3
|
+
const WAVE9_OPERATOR_CONTROLLED_BUNDLE = 'wave9_28d_momentum_reversal';
|
|
3
4
|
export async function toggleStrategyTool(args, deps) {
|
|
5
|
+
if (args.name === WAVE9_OPERATOR_CONTROLLED_BUNDLE) {
|
|
6
|
+
return {
|
|
7
|
+
error: 'Wave 9 activation is operator-controlled. Do not change this toggle; ' +
|
|
8
|
+
'the operator must use the Strategies dashboard.',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
4
11
|
return fetchIntelApi(`/api/strategies/${encodeURIComponent(args.name)}/toggle`, deps, {
|
|
5
12
|
method: 'PUT',
|
|
6
13
|
body: { active: args.active },
|
package/types.d.ts
CHANGED
|
@@ -69,6 +69,11 @@ export interface CcxtPosition {
|
|
|
69
69
|
timestamp: number;
|
|
70
70
|
datetime: string;
|
|
71
71
|
setupType?: string;
|
|
72
|
+
missionId?: string;
|
|
73
|
+
candidateId?: string;
|
|
74
|
+
strategy?: 'tsmom_28d_long_flat_reversal_1d' | 'tsmom_28d_short_flat_reversal_1d';
|
|
75
|
+
accruedFundingUsd?: number;
|
|
76
|
+
fundingThroughTime?: string;
|
|
72
77
|
thesis?: string;
|
|
73
78
|
stopPrice?: number;
|
|
74
79
|
targetPrice?: number;
|