@reefclaw/openclaw-plugin 0.1.4 → 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 +567 -57
- 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/persistence/state-manager.d.ts +24 -0
- package/persistence/state-manager.js +62 -4
- 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 +20 -3
- 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 +28 -9
- package/venues/registry.js +19 -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
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// ⚠️ GENERATED FILE — DO NOT EDIT.
|
|
2
|
+
// Canonical source of truth: shared/src/portfolio/wave9-policy.ts
|
|
3
|
+
// Regenerate: node scripts/sync-shared-code.mjs (enforced by shared-code-sync.test.ts)
|
|
4
|
+
//
|
|
5
|
+
// This copy exists because this package builds with tsc and deploys as a
|
|
6
|
+
// self-contained tree that strips workspace deps, so it cannot import
|
|
7
|
+
// @reefclaw/shared runtime code across the deploy boundary.
|
|
8
|
+
// Canonical frozen Wave 9 daily signal policy used by the default-off runtime parity
|
|
9
|
+
// observer. This deliberately calls the same generated condition registry and
|
|
10
|
+
// ATR implementation as the intelligence backtest instead of reimplementing
|
|
11
|
+
// momentum semantics in the plugin.
|
|
12
|
+
import { computeATR } from '../shared/indicators.js';
|
|
13
|
+
import { evaluateConditions } from '../signals/conditions/registry.js';
|
|
14
|
+
export const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
15
|
+
export const WAVE9_LOOKBACK_DAYS = 28;
|
|
16
|
+
export const WAVE9_ATR_PERIOD = 14;
|
|
17
|
+
export const WAVE9_ATR_MULTIPLE = 3;
|
|
18
|
+
export const WAVE9_CONTEXT_BARS = 250;
|
|
19
|
+
export const WAVE9_LONG_STRATEGY = 'tsmom_28d_long_flat_reversal_1d';
|
|
20
|
+
export const WAVE9_SHORT_STRATEGY = 'tsmom_28d_short_flat_reversal_1d';
|
|
21
|
+
export const WAVE9_SYMBOL_PRIORITY = [
|
|
22
|
+
'BTCUSDT',
|
|
23
|
+
'ETHUSDT',
|
|
24
|
+
'SOLUSDT',
|
|
25
|
+
'BNBUSDT',
|
|
26
|
+
'XRPUSDT',
|
|
27
|
+
'DOGEUSDT',
|
|
28
|
+
'ADAUSDT',
|
|
29
|
+
'LINKUSDT',
|
|
30
|
+
];
|
|
31
|
+
export const WAVE9_STRATEGY_PRIORITY = [
|
|
32
|
+
WAVE9_LONG_STRATEGY,
|
|
33
|
+
WAVE9_SHORT_STRATEGY,
|
|
34
|
+
];
|
|
35
|
+
function requireFinitePositive(value, label) {
|
|
36
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
37
|
+
throw new Error(`${label} must be a positive finite number`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Return the exact completed-daily context visible at a UTC daily boundary.
|
|
42
|
+
* The function is intentionally fail-closed: unordered, duplicated, gapped,
|
|
43
|
+
* non-UTC, or stale candles are rejected rather than silently repaired.
|
|
44
|
+
*/
|
|
45
|
+
export function completedDailyContext(rawBars, eventTime) {
|
|
46
|
+
const eventMs = eventTime.getTime();
|
|
47
|
+
if (!Number.isFinite(eventMs) || eventMs % DAY_MS !== 0) {
|
|
48
|
+
throw new Error('Wave 9 evaluation time must be a finite UTC daily boundary');
|
|
49
|
+
}
|
|
50
|
+
let previous = Number.NEGATIVE_INFINITY;
|
|
51
|
+
for (const [index, bar] of rawBars.entries()) {
|
|
52
|
+
const time = bar.time.getTime();
|
|
53
|
+
if (!Number.isFinite(time) || time % DAY_MS !== 0) {
|
|
54
|
+
throw new Error(`daily bar ${index} is not aligned to UTC midnight`);
|
|
55
|
+
}
|
|
56
|
+
if (time <= previous) {
|
|
57
|
+
throw new Error('daily bars must be strictly chronological and unique');
|
|
58
|
+
}
|
|
59
|
+
previous = time;
|
|
60
|
+
requireFinitePositive(bar.open, `daily bar ${index} open`);
|
|
61
|
+
requireFinitePositive(bar.high, `daily bar ${index} high`);
|
|
62
|
+
requireFinitePositive(bar.low, `daily bar ${index} low`);
|
|
63
|
+
requireFinitePositive(bar.close, `daily bar ${index} close`);
|
|
64
|
+
if (!Number.isFinite(bar.volume) || bar.volume < 0) {
|
|
65
|
+
throw new Error(`daily bar ${index} volume must be finite and non-negative`);
|
|
66
|
+
}
|
|
67
|
+
if (bar.high < Math.max(bar.open, bar.close) || bar.low > Math.min(bar.open, bar.close)) {
|
|
68
|
+
throw new Error(`daily bar ${index} has invalid OHLC geometry`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const completed = rawBars
|
|
72
|
+
.filter((bar) => bar.time.getTime() + DAY_MS <= eventMs)
|
|
73
|
+
.slice(-WAVE9_CONTEXT_BARS);
|
|
74
|
+
if (completed.length <= WAVE9_LOOKBACK_DAYS + 1) {
|
|
75
|
+
throw new Error(`Wave 9 requires at least ${WAVE9_LOOKBACK_DAYS + 2} completed daily bars`);
|
|
76
|
+
}
|
|
77
|
+
const expectedLastOpen = eventMs - DAY_MS;
|
|
78
|
+
const lastOpen = completed[completed.length - 1].time.getTime();
|
|
79
|
+
if (lastOpen !== expectedLastOpen) {
|
|
80
|
+
throw new Error(`latest completed daily bar must open at ${new Date(expectedLastOpen).toISOString()}`);
|
|
81
|
+
}
|
|
82
|
+
for (let index = 1; index < completed.length; index++) {
|
|
83
|
+
const prior = completed[index - 1].time.getTime();
|
|
84
|
+
const current = completed[index].time.getTime();
|
|
85
|
+
if (current - prior !== DAY_MS) {
|
|
86
|
+
throw new Error(`daily history has a gap between ${completed[index - 1].time.toISOString()} and ${completed[index].time.toISOString()}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return completed.map((bar) => ({ ...bar, time: new Date(bar.time) }));
|
|
90
|
+
}
|
|
91
|
+
function makeContext(bars, atr14) {
|
|
92
|
+
const latest = bars[bars.length - 1];
|
|
93
|
+
return {
|
|
94
|
+
symbol: 'WAVE9',
|
|
95
|
+
timestamp: new Date(latest.time.getTime() + DAY_MS),
|
|
96
|
+
ohlcv1h: bars,
|
|
97
|
+
ohlcv5m: [],
|
|
98
|
+
ohlcv4h: [],
|
|
99
|
+
ohlcv1d: bars,
|
|
100
|
+
currentPrice: latest.close,
|
|
101
|
+
atr14,
|
|
102
|
+
obImbalance: 0.5,
|
|
103
|
+
fundingRates: [],
|
|
104
|
+
fundingMean: 0,
|
|
105
|
+
fundingStd: 0.0001,
|
|
106
|
+
oiValues: [],
|
|
107
|
+
liqNearEntry: 0,
|
|
108
|
+
tradeFlow: [],
|
|
109
|
+
regime: 'UNKNOWN',
|
|
110
|
+
regimeConfidence: 0,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function conditionMet(context, type, params, direction) {
|
|
114
|
+
const result = evaluateConditions([{ type, params }], context, direction);
|
|
115
|
+
if (result.conditions.length !== 1 || result.conditions[0].name !== type) {
|
|
116
|
+
throw new Error(`canonical condition registry did not evaluate ${type}`);
|
|
117
|
+
}
|
|
118
|
+
return result.conditions[0].met;
|
|
119
|
+
}
|
|
120
|
+
/** Evaluate the exact Wave 9 entry-transition and signal-reversal semantics. */
|
|
121
|
+
export function evaluateWave9Daily(rawBars, eventTime) {
|
|
122
|
+
const bars = completedDailyContext(rawBars, eventTime);
|
|
123
|
+
const highs = bars.map((bar) => bar.high);
|
|
124
|
+
const lows = bars.map((bar) => bar.low);
|
|
125
|
+
const closes = bars.map((bar) => bar.close);
|
|
126
|
+
const atr14 = computeATR(highs, lows, closes, WAVE9_ATR_PERIOD);
|
|
127
|
+
requireFinitePositive(atr14, 'Wave 9 daily ATR(14)');
|
|
128
|
+
const context = makeContext(bars, atr14);
|
|
129
|
+
const currentClose = closes[closes.length - 1];
|
|
130
|
+
const currentPrior = closes[closes.length - 1 - WAVE9_LOOKBACK_DAYS];
|
|
131
|
+
const previousClose = closes[closes.length - 2];
|
|
132
|
+
const previousPrior = closes[closes.length - 2 - WAVE9_LOOKBACK_DAYS];
|
|
133
|
+
const currentReturn = (currentClose - currentPrior) / currentPrior;
|
|
134
|
+
const previousReturn = (previousClose - previousPrior) / previousPrior;
|
|
135
|
+
if (!Number.isFinite(currentReturn) || !Number.isFinite(previousReturn)) {
|
|
136
|
+
throw new Error('Wave 9 momentum inputs are not finite');
|
|
137
|
+
}
|
|
138
|
+
const longEntry = conditionMet(context, 'return_momentum_zero_cross', { tfHours: 24, lookback: WAVE9_LOOKBACK_DAYS, dirSign: 1 }, 'LONG');
|
|
139
|
+
const shortEntry = conditionMet(context, 'return_momentum_zero_cross', { tfHours: 24, lookback: WAVE9_LOOKBACK_DAYS, dirSign: -1 }, 'SHORT');
|
|
140
|
+
const exitLong = conditionMet(context, 'return_momentum', {
|
|
141
|
+
tfHours: 24,
|
|
142
|
+
lookback: WAVE9_LOOKBACK_DAYS,
|
|
143
|
+
minReturnPct: 0,
|
|
144
|
+
maxAbsReturnPct: 0,
|
|
145
|
+
dirSign: -1,
|
|
146
|
+
mode: 1,
|
|
147
|
+
}, 'SHORT');
|
|
148
|
+
const exitShort = conditionMet(context, 'return_momentum', {
|
|
149
|
+
tfHours: 24,
|
|
150
|
+
lookback: WAVE9_LOOKBACK_DAYS,
|
|
151
|
+
minReturnPct: 0,
|
|
152
|
+
maxAbsReturnPct: 0,
|
|
153
|
+
dirSign: 1,
|
|
154
|
+
mode: 1,
|
|
155
|
+
}, 'LONG');
|
|
156
|
+
const entries = [];
|
|
157
|
+
if (longEntry) {
|
|
158
|
+
entries.push({
|
|
159
|
+
strategy: WAVE9_LONG_STRATEGY,
|
|
160
|
+
direction: 'LONG',
|
|
161
|
+
stopPrice: currentClose - atr14 * WAVE9_ATR_MULTIPLE,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (shortEntry) {
|
|
165
|
+
entries.push({
|
|
166
|
+
strategy: WAVE9_SHORT_STRATEGY,
|
|
167
|
+
direction: 'SHORT',
|
|
168
|
+
stopPrice: currentClose + atr14 * WAVE9_ATR_MULTIPLE,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
eventTime: eventTime.toISOString(),
|
|
173
|
+
completedDailyOpen: bars[bars.length - 1].time.toISOString(),
|
|
174
|
+
currentReturn,
|
|
175
|
+
previousReturn,
|
|
176
|
+
atr14,
|
|
177
|
+
referencePrice: currentClose,
|
|
178
|
+
entries,
|
|
179
|
+
exitLong,
|
|
180
|
+
exitShort,
|
|
181
|
+
sourceBarCount: bars.length,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -777,6 +777,56 @@ register('return_momentum', (ctx, params, direction) => {
|
|
|
777
777
|
: `${lookback}-bar ${tfHours}h return ${(ret * 100).toFixed(2)}% fails ${label} threshold ${(minReturnPct * 100).toFixed(1)}%${capText}`,
|
|
778
778
|
};
|
|
779
779
|
});
|
|
780
|
+
// Strict return-momentum episode transition. Unlike return_momentum, this is
|
|
781
|
+
// an event rather than a level: it fires once when the lookback return crosses
|
|
782
|
+
// zero (or an explicit threshold), then remains false until the sign resets.
|
|
783
|
+
// That makes stop-outs and capacity-rejected entries naturally lock out for
|
|
784
|
+
// the rest of the same momentum episode.
|
|
785
|
+
register('return_momentum_zero_cross', (ctx, params, direction) => {
|
|
786
|
+
const lookback = Math.max(1, Math.floor(params.lookback ?? 28));
|
|
787
|
+
const thresholdPct = Math.max(0, params.thresholdPct ?? 0);
|
|
788
|
+
const dirSign = params.dirSign ?? 0;
|
|
789
|
+
const tfHours = params.tfHours ?? 24;
|
|
790
|
+
const bars = pickBars(ctx, tfHours);
|
|
791
|
+
if (bars.length <= lookback + 1) {
|
|
792
|
+
return {
|
|
793
|
+
met: false,
|
|
794
|
+
value: 0,
|
|
795
|
+
threshold: thresholdPct * 100,
|
|
796
|
+
description: `Insufficient ${tfHours}h bars for return_momentum_zero_cross`,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const currentClose = bars[bars.length - 1].close;
|
|
800
|
+
const currentPrior = bars[bars.length - 1 - lookback].close;
|
|
801
|
+
const previousClose = bars[bars.length - 2].close;
|
|
802
|
+
const previousPrior = bars[bars.length - 2 - lookback].close;
|
|
803
|
+
const currentReturn = currentPrior > 0
|
|
804
|
+
? (currentClose - currentPrior) / currentPrior
|
|
805
|
+
: Number.NaN;
|
|
806
|
+
const previousReturn = previousPrior > 0
|
|
807
|
+
? (previousClose - previousPrior) / previousPrior
|
|
808
|
+
: Number.NaN;
|
|
809
|
+
const sign = dirSign !== 0
|
|
810
|
+
? Math.sign(dirSign)
|
|
811
|
+
: direction === 'LONG' ? 1 : direction === 'SHORT' ? -1 : 0;
|
|
812
|
+
const met = Number.isFinite(currentReturn)
|
|
813
|
+
&& Number.isFinite(previousReturn)
|
|
814
|
+
&& (sign > 0
|
|
815
|
+
? previousReturn <= thresholdPct && currentReturn > thresholdPct
|
|
816
|
+
: sign < 0
|
|
817
|
+
? previousReturn >= -thresholdPct && currentReturn < -thresholdPct
|
|
818
|
+
: false);
|
|
819
|
+
return {
|
|
820
|
+
met,
|
|
821
|
+
value: Number.isFinite(currentReturn)
|
|
822
|
+
? Math.round(currentReturn * 10000) / 100
|
|
823
|
+
: 0,
|
|
824
|
+
threshold: thresholdPct * 100,
|
|
825
|
+
description: Number.isFinite(currentReturn) && Number.isFinite(previousReturn)
|
|
826
|
+
? `${lookback}-bar ${tfHours}h return moved from ${(previousReturn * 100).toFixed(2)}% to ${(currentReturn * 100).toFixed(2)}%`
|
|
827
|
+
: `Invalid ${tfHours}h prices for return_momentum_zero_cross`,
|
|
828
|
+
};
|
|
829
|
+
});
|
|
780
830
|
// ─── 32. rsi_multi_pivot_divergence ──────────────────────────────────
|
|
781
831
|
// Strict N-pivot RSI divergence: price makes successively lower lows (or
|
|
782
832
|
// higher highs) while RSI makes the opposite, with optional volume dry-up
|
|
@@ -205,6 +205,11 @@ export class ExchangeSimulator extends EventEmitter {
|
|
|
205
205
|
// Surface entry metadata (if available)
|
|
206
206
|
...(p.metadata && {
|
|
207
207
|
setupType: p.metadata.setupType,
|
|
208
|
+
missionId: p.metadata.missionId,
|
|
209
|
+
candidateId: p.metadata.candidateId,
|
|
210
|
+
strategy: p.metadata.strategy,
|
|
211
|
+
accruedFundingUsd: p.metadata.accruedFundingUsd,
|
|
212
|
+
fundingThroughTime: p.metadata.fundingThroughTime,
|
|
208
213
|
thesis: p.metadata.thesis,
|
|
209
214
|
stopPrice: p.metadata.stopPrice,
|
|
210
215
|
targetPrice: p.metadata.targetPrice,
|
package/simulator/fill-engine.js
CHANGED
|
@@ -53,7 +53,11 @@ export function parseSymbol(symbol) {
|
|
|
53
53
|
if (parts.length !== 2) {
|
|
54
54
|
throw new Error(`Invalid symbol format: ${symbol}. Expected BASE/QUOTE`);
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
const [quote, settle, ...extra] = parts[1].split(':');
|
|
57
|
+
if (!quote || extra.length > 0 || (settle !== undefined && settle !== quote)) {
|
|
58
|
+
throw new Error(`Invalid symbol format: ${symbol}. Expected BASE/QUOTE or BASE/QUOTE:QUOTE`);
|
|
59
|
+
}
|
|
60
|
+
return { base: parts[0], quote };
|
|
57
61
|
}
|
|
58
62
|
/**
|
|
59
63
|
* Ensure a currency exists in the wallet with at least zero balance.
|
package/simulator/types.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export interface CurrencyBalance {
|
|
|
83
83
|
* - `exchange_stop` / `exchange_target`: exchange-native STOP_MARKET / TAKE_PROFIT_MARKET triggered on Binance
|
|
84
84
|
* - `cancelled_auto`: bracket reconciler cleaned up after detecting the position closed externally
|
|
85
85
|
* - `operator` / `agent` / `emergency`: explicit initiator */
|
|
86
|
-
export type CloseReason = 'agent' | 'stop_watcher' | 'emergency' | 'operator' | 'bracket_attach_failed' | 'exchange_stop' | 'exchange_target' | 'cancelled_auto';
|
|
86
|
+
export type CloseReason = 'agent' | 'stop_watcher' | 'emergency' | 'operator' | 'bracket_attach_failed' | 'exchange_stop' | 'exchange_target' | 'cancelled_auto' | 'wave9_signal_reversal' | 'liquidated' | 'adl' | 'venue_delisted';
|
|
87
87
|
/** The agent's stated profit-realization plan, pinned at entry. PURE DATA /
|
|
88
88
|
* indication — the plugin does NOT enforce it; it is surfaced back so the agent
|
|
89
89
|
* can hold to its own plan instead of re-litigating. The agent fills the parts
|
|
@@ -107,6 +107,11 @@ export interface RealizationRule {
|
|
|
107
107
|
* moment of close. */
|
|
108
108
|
export interface PositionMetadata {
|
|
109
109
|
setupType?: string;
|
|
110
|
+
missionId?: string;
|
|
111
|
+
candidateId?: string;
|
|
112
|
+
strategy?: 'tsmom_28d_long_flat_reversal_1d' | 'tsmom_28d_short_flat_reversal_1d';
|
|
113
|
+
accruedFundingUsd?: number;
|
|
114
|
+
fundingThroughTime?: string;
|
|
110
115
|
thesis?: string;
|
|
111
116
|
stopPrice?: number;
|
|
112
117
|
targetPrice?: number;
|
package/strategy/evaluator.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface StrategyConfig {
|
|
|
27
27
|
params: Record<string, number>;
|
|
28
28
|
};
|
|
29
29
|
targetRMultiples: number[];
|
|
30
|
+
/** Dedicated portfolio catalog rows are not declarative generic strategies. */
|
|
31
|
+
executionModel?: string;
|
|
30
32
|
}
|
|
31
33
|
export interface StrategyRow {
|
|
32
34
|
id: number;
|
|
@@ -55,6 +57,7 @@ export interface StrategyEvalResult {
|
|
|
55
57
|
regime_confidence: number;
|
|
56
58
|
summary: string;
|
|
57
59
|
}
|
|
60
|
+
export declare function isDeclarativeStrategyConfig(strategy: StrategyConfig): boolean;
|
|
58
61
|
/**
|
|
59
62
|
* Evaluate a strategy against a symbol's fact object.
|
|
60
63
|
* Returns null if the strategy is regime-gated and the symbol's regime doesn't match.
|
package/strategy/evaluator.js
CHANGED
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
// and optionally checks threshold-based conditions against indicator values.
|
|
5
5
|
import { CONDITION_EVALUATORS } from './condition-registry.js';
|
|
6
6
|
import { getCachedTradingParams } from '../trading-params-cache.js';
|
|
7
|
+
export function isDeclarativeStrategyConfig(strategy) {
|
|
8
|
+
return strategy.executionModel === undefined || strategy.executionModel === 'declarative';
|
|
9
|
+
}
|
|
7
10
|
// ─── Evaluator ──────────────────────────────────────────────────────────
|
|
8
11
|
/**
|
|
9
12
|
* Evaluate a strategy against a symbol's fact object.
|
|
10
13
|
* Returns null if the strategy is regime-gated and the symbol's regime doesn't match.
|
|
11
14
|
*/
|
|
12
15
|
export function evaluateStrategy(strategy, fact) {
|
|
16
|
+
if (!isDeclarativeStrategyConfig(strategy))
|
|
17
|
+
return null;
|
|
13
18
|
// Check regime gate
|
|
14
19
|
if (strategy.regimeWhitelist.length > 0) {
|
|
15
20
|
const matchesRegime = strategy.regimeWhitelist.some(r => r === fact.regime || r === 'ANY');
|
|
@@ -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 AttachBracketsArgs {
|
|
3
5
|
symbol: string;
|
|
4
6
|
stop_price?: number;
|
|
@@ -26,9 +28,12 @@ interface AttachBracketsSuccess {
|
|
|
26
28
|
idempotent_no_op?: boolean;
|
|
27
29
|
note?: string;
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
interface AttachBracketsDeps {
|
|
30
32
|
adapter: IExchangeAdapter;
|
|
31
|
-
|
|
33
|
+
operationLock?: TradingOperationLock;
|
|
34
|
+
checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
|
|
35
|
+
}
|
|
36
|
+
export declare function attachBracketsTool(args: AttachBracketsArgs, deps: AttachBracketsDeps): Promise<AttachBracketsSuccess | {
|
|
32
37
|
error: string;
|
|
33
38
|
}>;
|
|
34
39
|
export {};
|
package/tools/attach-brackets.js
CHANGED
|
@@ -41,6 +41,42 @@ export async function attachBracketsTool(args, deps) {
|
|
|
41
41
|
if (!deps.adapter.isLive) {
|
|
42
42
|
return { error: 'attach_brackets is live-mode only (it places exchange-side STOP/TP orders). In paper mode, use modify_stop / modify_target to set or move the position\'s protective levels in place — the paper stop-watcher enforces metadata.stopPrice.' };
|
|
43
43
|
}
|
|
44
|
+
if (deps.checkWave9LiveSymbolOwnership && !deps.operationLock) {
|
|
45
|
+
return {
|
|
46
|
+
error: 'Live attach_brackets refused: the shared trading operation lock is required ' +
|
|
47
|
+
'while Wave 9 LIVE ownership checks are enabled.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const attachLive = () => attachBracketsLive(args, deps);
|
|
51
|
+
try {
|
|
52
|
+
return deps.operationLock
|
|
53
|
+
? await deps.operationLock.withAccountLock(`generic-attach-brackets-final:${args.symbol}`, attachLive)
|
|
54
|
+
: await attachLive();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return { error: formatError(err) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function attachBracketsLive(args, deps) {
|
|
61
|
+
if (deps.checkWave9LiveSymbolOwnership) {
|
|
62
|
+
let ownership;
|
|
63
|
+
try {
|
|
64
|
+
ownership = await deps.checkWave9LiveSymbolOwnership(args.symbol);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
return {
|
|
68
|
+
error: `attach_brackets refused at mutation boundary: Wave 9 LIVE ownership check failed ` +
|
|
69
|
+
`for ${args.symbol} (${formatError(err)}).`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
|
|
73
|
+
return {
|
|
74
|
+
error: `attach_brackets refused at mutation boundary: ${args.symbol} has ` +
|
|
75
|
+
`${ownership.status} Wave 9 LIVE ownership (${ownership.reason}). ` +
|
|
76
|
+
'Wave 9 protection is candidate-bound and cannot be replaced by the generic bracket tool.',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
44
80
|
const live = deps.adapter;
|
|
45
81
|
const mgr = live.getBracketManager?.();
|
|
46
82
|
const ledger = live.getBracketLedger?.();
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
2
3
|
import type { CcxtOrder } from '../types.js';
|
|
3
4
|
export declare function cancelAllOrdersTool(args: {
|
|
4
5
|
symbol?: string;
|
|
5
6
|
}, deps: {
|
|
6
7
|
adapter: IExchangeAdapter;
|
|
8
|
+
operationLock?: TradingOperationLock;
|
|
7
9
|
}): Promise<CcxtOrder[]>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Tool: cancel_all_orders — cancel all open orders (paper or live)
|
|
2
2
|
// NO readiness gate — emergency control (kill switch), must always work.
|
|
3
3
|
export async function cancelAllOrdersTool(args, deps) {
|
|
4
|
-
|
|
4
|
+
const cancel = () => deps.adapter.cancelAllOrders(args.symbol);
|
|
5
|
+
return deps.adapter.isLive && deps.operationLock
|
|
6
|
+
? deps.operationLock.withAccountLock(`generic-cancel-all-orders-final:${args.symbol ?? 'all'}`, cancel)
|
|
7
|
+
: cancel();
|
|
5
8
|
}
|
package/tools/cancel-order.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { IExchangeAdapter } from '../exchange-adapter.js';
|
|
2
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
2
3
|
import type { CcxtOrder } from '../types.js';
|
|
4
|
+
import type { Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
|
|
3
5
|
export declare function cancelOrderTool(args: {
|
|
4
6
|
id: string;
|
|
5
7
|
symbol?: string;
|
|
6
8
|
}, deps: {
|
|
7
9
|
adapter: IExchangeAdapter;
|
|
10
|
+
operationLock?: TradingOperationLock;
|
|
11
|
+
checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
|
|
8
12
|
}): Promise<CcxtOrder | {
|
|
9
13
|
error: string;
|
|
10
14
|
}>;
|
package/tools/cancel-order.js
CHANGED
|
@@ -1,12 +1,58 @@
|
|
|
1
|
-
// Tool: cancel_order — cancel a single order (paper or live)
|
|
2
|
-
//
|
|
1
|
+
// Tool: cancel_order — cancel a single order (paper or live).
|
|
2
|
+
// There is no readiness gate, but LIVE cancellation cannot individually strip
|
|
3
|
+
// candidate-bound Wave 9 protection. The emergency escape is an explicit
|
|
4
|
+
// operator close, which reduces exposure instead of leaving it naked.
|
|
3
5
|
import { formatError } from '../logger.js';
|
|
4
6
|
export async function cancelOrderTool(args, deps) {
|
|
5
7
|
if (!args.id) {
|
|
6
8
|
return { error: 'Order ID is required.' };
|
|
7
9
|
}
|
|
10
|
+
if (!deps.adapter.isLive) {
|
|
11
|
+
try {
|
|
12
|
+
return await deps.adapter.cancelOrder(args.id, args.symbol);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
return { error: formatError(err) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (deps.checkWave9LiveSymbolOwnership && !deps.operationLock) {
|
|
19
|
+
return {
|
|
20
|
+
error: 'Live cancel_order refused: the shared trading operation lock is required ' +
|
|
21
|
+
'while Wave 9 LIVE ownership checks are enabled.',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const cancelLive = async () => {
|
|
25
|
+
let symbol = args.symbol?.trim();
|
|
26
|
+
if (!symbol) {
|
|
27
|
+
const matches = (await deps.adapter.getOpenOrders())
|
|
28
|
+
.filter((order) => order.id === args.id);
|
|
29
|
+
if (matches.length !== 1 || !matches[0].symbol) {
|
|
30
|
+
throw new Error(`cancel_order refused: order ${args.id} could not be resolved to exactly one ` +
|
|
31
|
+
`trusted open-order symbol (matches=${matches.length}).`);
|
|
32
|
+
}
|
|
33
|
+
symbol = matches[0].symbol;
|
|
34
|
+
}
|
|
35
|
+
if (deps.checkWave9LiveSymbolOwnership) {
|
|
36
|
+
let ownership;
|
|
37
|
+
try {
|
|
38
|
+
ownership = await deps.checkWave9LiveSymbolOwnership(symbol);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
throw new Error(`cancel_order refused at mutation boundary: Wave 9 LIVE ownership check failed ` +
|
|
42
|
+
`for ${symbol} (${formatError(err)}).`);
|
|
43
|
+
}
|
|
44
|
+
if (ownership.status === 'owned' || ownership.status === 'ambiguous') {
|
|
45
|
+
throw new Error(`cancel_order refused at mutation boundary: ${symbol} has ` +
|
|
46
|
+
`${ownership.status} Wave 9 LIVE ownership (${ownership.reason}). ` +
|
|
47
|
+
'Use operator_command to close the position instead of stripping candidate-bound protection.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return deps.adapter.cancelOrder(args.id, symbol);
|
|
51
|
+
};
|
|
8
52
|
try {
|
|
9
|
-
return
|
|
53
|
+
return deps.operationLock
|
|
54
|
+
? await deps.operationLock.withAccountLock(`generic-cancel-order-final:${args.id}`, cancelLive)
|
|
55
|
+
: await cancelLive();
|
|
10
56
|
}
|
|
11
57
|
catch (err) {
|
|
12
58
|
return { error: formatError(err) };
|
|
@@ -4,6 +4,11 @@ import type { CcxtOrder } from '../types.js';
|
|
|
4
4
|
import { type ClosePositionArgs } from './assessment-validation.js';
|
|
5
5
|
import { type AutoCaptureContext } from '../ingest/position-auto-capture.js';
|
|
6
6
|
import { type ExitGateMode } from '../config/position-review-config.js';
|
|
7
|
+
import type { TradingOperationLock } from '../lifecycle/trading-operation-lock.js';
|
|
8
|
+
import type { Wave9LiveExecutionLedger } from '../wave9/live-execution-ledger.js';
|
|
9
|
+
import type { Wave9LiveSymbolOwnershipCheck } from '../wave9/live-symbol-ownership.js';
|
|
10
|
+
import { type Wave9LiveResidualProtectionResult } from '../wave9/live-residual-protection.js';
|
|
11
|
+
import { type Wave9PaperAdmissionGuard } from '../wave9/paper-admission-guard.js';
|
|
7
12
|
export declare function closePositionTool(args: ClosePositionArgs, deps: {
|
|
8
13
|
binanceApi: PublicMarketDataApi;
|
|
9
14
|
adapter: IExchangeAdapter;
|
|
@@ -11,6 +16,24 @@ export declare function closePositionTool(args: ClosePositionArgs, deps: {
|
|
|
11
16
|
/** Override for tests — production reads from plugin-config.json on each
|
|
12
17
|
* call so the flag can be flipped without a restart. */
|
|
13
18
|
exitGateMode?: ExitGateMode;
|
|
19
|
+
wave9AdmissionGuard?: Wave9PaperAdmissionGuard;
|
|
20
|
+
refreshPaperState?: () => void;
|
|
21
|
+
wave9TradingModeCheck?: () => string;
|
|
22
|
+
wave9VenueCheck?: () => string;
|
|
23
|
+
captureWave9PositionFingerprint?: (symbol: string) => Promise<{
|
|
24
|
+
fingerprint: string;
|
|
25
|
+
position: {
|
|
26
|
+
candidateId?: string;
|
|
27
|
+
missionId?: string;
|
|
28
|
+
setupType?: string;
|
|
29
|
+
side: 'long' | 'short';
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
wave9LiveLedger?: Wave9LiveExecutionLedger;
|
|
33
|
+
checkWave9LiveSymbolOwnership?: Wave9LiveSymbolOwnershipCheck;
|
|
34
|
+
reprotectWave9LiveResidual?: (adapter: IExchangeAdapter, ledger: Wave9LiveExecutionLedger, candidateId: string, symbol: string) => Promise<Wave9LiveResidualProtectionResult>;
|
|
35
|
+
operationLock?: TradingOperationLock;
|
|
36
|
+
operationLockHeld?: boolean;
|
|
14
37
|
}): Promise<CcxtOrder | {
|
|
15
38
|
error: string;
|
|
16
39
|
}>;
|