@liquiditytech/rapidx-cli 1.0.34 → 1.0.36
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/README.md +8 -0
- package/dist/cli/commands/automation.js +36 -0
- package/dist/cli/commands/index.js +4 -0
- package/dist/cli/commands/order.js +54 -6
- package/dist/cli/help.js +2 -1
- package/dist/cli/parser.js +1 -1
- package/dist/core/automation/session.js +455 -0
- package/dist/core/contracts/capabilities.js +5 -0
- package/dist/core/contracts/compatibility.js +1 -1
- package/dist/core/contracts/input-schema.js +35 -0
- package/dist/core/index.js +1 -0
- package/dist/core/safety/policy.js +6 -1
- package/dist/core/trading/preview-preflight.js +10 -2
- package/dist/core/trading/preview.js +69 -40
- package/dist/core/version.js +1 -1
- package/dist/mcp/tool-registry.js +8 -3
- package/dist/mcp/tool-runner.js +91 -8
- package/package.json +1 -1
- package/packages/distribution/docs/cli.md +3 -4
- package/packages/distribution/docs/mcp.md +2 -2
- package/packages/distribution/docs/quickstart.md +7 -1
- package/packages/distribution/docs/tools.md +9 -2
- package/packages/distribution/manifests/offline-manifest.json +4 -4
- package/packages/distribution/registry/rapidx.mcp.json +1 -1
|
@@ -8,6 +8,11 @@ export const CAPABILITIES = [
|
|
|
8
8
|
{ capabilityId: "auth.check", cliCommand: "rapidx auth check", mcpTool: "rapidx/self-check", operationType: "DIAGNOSTIC", riskLevel: "read", inputSchema: "AuthInput", outputSchema: "AuthResult", previewRequired: false },
|
|
9
9
|
{ capabilityId: "doctor.run", cliCommand: "rapidx doctor --json", mcpTool: "rapidx/self-check", operationType: "DIAGNOSTIC", riskLevel: "read", inputSchema: "DoctorInput", outputSchema: "DoctorReport", previewRequired: false },
|
|
10
10
|
{ capabilityId: "update.check", cliCommand: "rapidx update check", mcpTool: "rapidx/update/check", operationType: "DIAGNOSTIC", riskLevel: "read", inputSchema: "UpdateCheckInput", outputSchema: "UpdateCheckResult", previewRequired: false },
|
|
11
|
+
{ capabilityId: "automation.start", cliCommand: "rapidx automation start", mcpTool: "rapidx/automation/start", operationType: "TRADE_WRITE", riskLevel: "critical-trade-write", inputSchema: "AutomationStartInput", outputSchema: "AutomationSession", previewRequired: false },
|
|
12
|
+
{ capabilityId: "automation.list", cliCommand: "rapidx automation list", mcpTool: "rapidx/automation/list", operationType: "READ", riskLevel: "read", inputSchema: "AutomationListInput", outputSchema: "AutomationSessionList", previewRequired: false },
|
|
13
|
+
{ capabilityId: "automation.status", cliCommand: "rapidx automation status", mcpTool: "rapidx/automation/status", operationType: "READ", riskLevel: "read", inputSchema: "AutomationStatusInput", outputSchema: "AutomationSession", previewRequired: false },
|
|
14
|
+
{ capabilityId: "automation.extend", cliCommand: "rapidx automation extend", mcpTool: "rapidx/automation/extend", operationType: "TRADE_WRITE", riskLevel: "critical-trade-write", inputSchema: "AutomationExtendInput", outputSchema: "AutomationSession", previewRequired: false },
|
|
15
|
+
{ capabilityId: "automation.stop", cliCommand: "rapidx automation stop", mcpTool: "rapidx/automation/stop", operationType: "DIAGNOSTIC", riskLevel: "write-config", inputSchema: "AutomationStopInput", outputSchema: "AutomationSession", previewRequired: false },
|
|
11
16
|
{ capabilityId: "schema.discover", cliCommand: "rapidx schema --json", mcpTool: "rapidx/tools", operationType: "READ", riskLevel: "read", inputSchema: "SchemaQuery", outputSchema: "SchemaResult", previewRequired: false },
|
|
12
17
|
{ capabilityId: "self-check.run", cliCommand: "rapidx self-check --read-only --json", mcpTool: "rapidx/self-check", operationType: "READ", riskLevel: "read", inputSchema: "SelfCheckInput", outputSchema: "SelfCheckReport", previewRequired: false },
|
|
13
18
|
{ capabilityId: "market.ticker", cliCommand: "rapidx market get-ticker", mcpTool: "rapidx/market/get-ticker", operationType: "READ", riskLevel: "read", inputSchema: "SymbolInput", outputSchema: "MarketTicker", previewRequired: false },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { SCHEMA_VERSION } from "./types.js";
|
|
2
2
|
import { RAPIDX_VERSION } from "../version.js";
|
|
3
3
|
export const RAPIDX_SKILLS_DISTRIBUTION = "github";
|
|
4
|
-
export const RAPIDX_SKILLS_VERSION = "1.0.
|
|
4
|
+
export const RAPIDX_SKILLS_VERSION = "1.0.10";
|
|
5
5
|
export const RAPIDX_SKILLS_SCHEMA_VERSION = "1.0.0";
|
|
6
6
|
export function buildCompatibilityReport(input = {}) {
|
|
7
7
|
const checks = [
|
|
@@ -68,6 +68,10 @@ const automationScopeSchema = {
|
|
|
68
68
|
type: "string",
|
|
69
69
|
description: "Optional automation scope label, for example single-preview, strategy-session, or the user's exact scope phrase."
|
|
70
70
|
};
|
|
71
|
+
const automationSessionIdSchema = {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Automation session id returned by rapidx automation start. Preview binds to this session and submit re-checks the session before writing."
|
|
74
|
+
};
|
|
71
75
|
const previewIdSchema = {
|
|
72
76
|
type: "string",
|
|
73
77
|
description: "Preview id returned by the matching preview tool or command.",
|
|
@@ -118,6 +122,9 @@ const closePositionReduceOnlySchema = {
|
|
|
118
122
|
};
|
|
119
123
|
const algoConditionTypeSchema = { type: "string", enum: ["CONDITIONAL", "OCO", "ENTIRE_CLOSE_POSITION", "PARTIAL_CLOSE_POSITION"] };
|
|
120
124
|
const triggerTypeSchema = { type: "string", enum: ["LAST_PRICE", "MARK_PRICE"] };
|
|
125
|
+
const symbolsArraySchema = { type: "array", items: symbolSchema, description: "RapidX symbols authorized for this automation session." };
|
|
126
|
+
const stringArraySchema = { type: "array", items: stringSchema };
|
|
127
|
+
const expiresInSecondsSchema = { type: "number", description: "Automation session duration in seconds. Default is 86400; maximum is 2592000." };
|
|
121
128
|
export function inputSchemaForName(name) {
|
|
122
129
|
switch (name) {
|
|
123
130
|
case "EmptyInput":
|
|
@@ -312,6 +319,30 @@ export function inputSchemaForName(name) {
|
|
|
312
319
|
return objectSchema({ commandLine: stringSchema, preflightError: stringSchema });
|
|
313
320
|
case "UpdateCheckInput":
|
|
314
321
|
return objectSchema({ force: booleanSchema, manifestUrl: stringSchema, maxCacheAgeSeconds: numberSchema, installedSkillsVersion: stringSchema });
|
|
322
|
+
case "AutomationStartInput":
|
|
323
|
+
return objectSchema({
|
|
324
|
+
symbols: symbolsArraySchema,
|
|
325
|
+
maxNotionalPerOrder: maxNotionalSchema,
|
|
326
|
+
maxTotalNotional: maxNotionalSchema,
|
|
327
|
+
expiresInSeconds: expiresInSecondsSchema,
|
|
328
|
+
allowedActions: stringArraySchema,
|
|
329
|
+
allowedOrderTypes: stringArraySchema,
|
|
330
|
+
name: stringSchema,
|
|
331
|
+
explicitUserConsent: explicitUserConsentSchema,
|
|
332
|
+
acceptedRiskText: acceptedRiskTextSchema
|
|
333
|
+
}, ["symbols", "maxNotionalPerOrder", "maxTotalNotional", "explicitUserConsent", "acceptedRiskText"]);
|
|
334
|
+
case "AutomationListInput":
|
|
335
|
+
return objectSchema({});
|
|
336
|
+
case "AutomationStatusInput":
|
|
337
|
+
case "AutomationStopInput":
|
|
338
|
+
return objectSchema({ automationSessionId: automationSessionIdSchema }, ["automationSessionId"]);
|
|
339
|
+
case "AutomationExtendInput":
|
|
340
|
+
return objectSchema({
|
|
341
|
+
automationSessionId: automationSessionIdSchema,
|
|
342
|
+
expiresInSeconds: expiresInSecondsSchema,
|
|
343
|
+
explicitUserConsent: explicitUserConsentSchema,
|
|
344
|
+
acceptedRiskText: acceptedRiskTextSchema
|
|
345
|
+
}, ["automationSessionId", "expiresInSeconds", "explicitUserConsent", "acceptedRiskText"]);
|
|
315
346
|
case "SchemaQuery":
|
|
316
347
|
return objectSchema({ consumer: stringSchema, consumerVersion: stringSchema, includeExamples: booleanSchema });
|
|
317
348
|
case "SelfCheckInput":
|
|
@@ -432,6 +463,7 @@ function tradeCreateProperties() {
|
|
|
432
463
|
}
|
|
433
464
|
function automationProperties() {
|
|
434
465
|
return {
|
|
466
|
+
automationSessionId: automationSessionIdSchema,
|
|
435
467
|
automationMode: automationModeSchema,
|
|
436
468
|
automationConsentText: automationConsentTextSchema,
|
|
437
469
|
automationScope: automationScopeSchema
|
|
@@ -451,6 +483,9 @@ function matchesType(value, schema) {
|
|
|
451
483
|
if (!schema.type) {
|
|
452
484
|
return true;
|
|
453
485
|
}
|
|
486
|
+
if (schema.type === "array") {
|
|
487
|
+
return Array.isArray(value);
|
|
488
|
+
}
|
|
454
489
|
return typeof value === schema.type;
|
|
455
490
|
}
|
|
456
491
|
function hasValue(value) {
|
package/dist/core/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./client/order-id.js";
|
|
|
16
16
|
export * from "./client/symbol.js";
|
|
17
17
|
export * from "./safety/policy.js";
|
|
18
18
|
export * from "./safety/raw-api.js";
|
|
19
|
+
export * from "./automation/session.js";
|
|
19
20
|
export * from "./trading/preview.js";
|
|
20
21
|
export * from "./trading/preview-preflight.js";
|
|
21
22
|
export * from "./update/check-update.js";
|
|
@@ -92,7 +92,7 @@ export function evaluateSafety(capability, input, policy, state = makeSafetyStat
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
const clientOrderId = typeof input.clientOrderId === "string" ? input.clientOrderId : undefined;
|
|
95
|
-
if (clientOrderId) {
|
|
95
|
+
if (clientOrderId && tracksDuplicateClientOrderId(capability)) {
|
|
96
96
|
const now = state.nowMs();
|
|
97
97
|
const seenAt = state.recentClientOrderIds.get(clientOrderId);
|
|
98
98
|
if (seenAt && now - seenAt < policy.duplicateWindowMs) {
|
|
@@ -168,6 +168,11 @@ function requiresNotionalLimit(capability) {
|
|
|
168
168
|
|| capability.capabilityId === "algo.place"
|
|
169
169
|
|| capability.capabilityId === "position.close";
|
|
170
170
|
}
|
|
171
|
+
function tracksDuplicateClientOrderId(capability) {
|
|
172
|
+
return capability.capabilityId === "order.place-preview"
|
|
173
|
+
|| capability.capabilityId === "order.place"
|
|
174
|
+
|| capability.capabilityId === "algo.place";
|
|
175
|
+
}
|
|
171
176
|
function estimateDirectNotional(input) {
|
|
172
177
|
const symbol = typeof input.symbol === "string" ? input.symbol : "";
|
|
173
178
|
if (!symbol.startsWith("BINANCE_")) {
|
|
@@ -170,7 +170,11 @@ function validateQuantity(input, rules) {
|
|
|
170
170
|
const quantityText = String(quantityValue);
|
|
171
171
|
const quantity = positiveNumber(quantityText);
|
|
172
172
|
if (quantity === undefined) {
|
|
173
|
-
|
|
173
|
+
throw new ProductError({
|
|
174
|
+
code: "RCORE24006",
|
|
175
|
+
status: "BLOCKED",
|
|
176
|
+
message: `INVALID_QUANTITY: quantity ${quantityText} must be a positive finite number.`
|
|
177
|
+
});
|
|
174
178
|
}
|
|
175
179
|
const minSize = positiveNumber(rules.minSize);
|
|
176
180
|
if (minSize !== undefined && quantity < minSize - 1e-12) {
|
|
@@ -204,7 +208,11 @@ function validatePrice(input, rules) {
|
|
|
204
208
|
const priceText = String(input.price);
|
|
205
209
|
const price = positiveNumber(priceText);
|
|
206
210
|
if (price === undefined) {
|
|
207
|
-
|
|
211
|
+
throw new ProductError({
|
|
212
|
+
code: "RCORE24007",
|
|
213
|
+
status: "BLOCKED",
|
|
214
|
+
message: `INVALID_PRICE: price ${priceText} must be a positive finite number.`
|
|
215
|
+
});
|
|
208
216
|
}
|
|
209
217
|
const tickSize = positiveNumber(rules.tickSize);
|
|
210
218
|
if (tickSize !== undefined && !isAlignedToStep(price, tickSize)) {
|
|
@@ -5,6 +5,7 @@ import { assertInputMatchesSchema } from "../contracts/input-schema.js";
|
|
|
5
5
|
import { evaluateSafety, stableParamsHash } from "../safety/policy.js";
|
|
6
6
|
import { ProductError } from "../errors/product-error.js";
|
|
7
7
|
import { parseRapidXSymbol } from "../client/symbol.js";
|
|
8
|
+
import { automationPreviewDetails } from "../automation/session.js";
|
|
8
9
|
export function makePreviewStore() {
|
|
9
10
|
return { records: new Map() };
|
|
10
11
|
}
|
|
@@ -37,13 +38,14 @@ export function savePreviewStoreToFile(filePath, store) {
|
|
|
37
38
|
const records = [...store.records.values()];
|
|
38
39
|
writeFileSync(filePath, `${JSON.stringify(records, null, 2)}\n`, { mode: 0o600 });
|
|
39
40
|
}
|
|
40
|
-
export function createTradePreview(capability, input, policy, state, store, ttlSeconds = 300) {
|
|
41
|
+
export function createTradePreview(capability, input, policy, state, store, ttlSeconds = 300, options = {}) {
|
|
41
42
|
assertInputMatchesSchema(capability.inputSchema, input, {
|
|
42
|
-
allowedExtraFields: ["explicitUserConsent", "acceptedRiskText", "automationMode", "automationConsentText", "automationScope"]
|
|
43
|
+
allowedExtraFields: ["explicitUserConsent", "acceptedRiskText", "automationMode", "automationConsentText", "automationScope", "automationSessionId"]
|
|
43
44
|
});
|
|
44
45
|
const tradeParams = normalizeTradeParams(input);
|
|
45
46
|
const automation = makeAutomationDetails(input, tradeParams);
|
|
46
|
-
const
|
|
47
|
+
const automationSession = options.automationSession ? automationPreviewDetails(options.automationSession) : undefined;
|
|
48
|
+
const previewDetails = makePreviewDetails(capability, tradeParams, automation, automationSession);
|
|
47
49
|
const decision = evaluateSafety(capability, input, policy, state);
|
|
48
50
|
if (!decision.allowed) {
|
|
49
51
|
throw new ProductError({
|
|
@@ -61,13 +63,44 @@ export function createTradePreview(capability, input, policy, state, store, ttlS
|
|
|
61
63
|
expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
|
|
62
64
|
submitted: false,
|
|
63
65
|
status: "PASS",
|
|
64
|
-
confirmation: makeSubmitConfirmation(previewId, automation),
|
|
65
|
-
...(automation ? { automation } : {})
|
|
66
|
+
confirmation: makeSubmitConfirmation(previewId, automation, automationSession),
|
|
67
|
+
...(automation ? { automation } : {}),
|
|
68
|
+
...(automationSession ? { automationSession } : {})
|
|
66
69
|
};
|
|
67
70
|
store.records.set(previewId, record);
|
|
68
71
|
return record;
|
|
69
72
|
}
|
|
70
|
-
export function createTargetedTradePreview(targetCapability, input, policy, state, store, ttlSeconds = 300) {
|
|
73
|
+
export function createTargetedTradePreview(targetCapability, input, policy, state, store, ttlSeconds = 300, options = {}) {
|
|
74
|
+
assertTargetedTradePreviewInput(targetCapability, input);
|
|
75
|
+
const tradeParams = normalizeTradeParams(input);
|
|
76
|
+
const automation = makeAutomationDetails(input, tradeParams);
|
|
77
|
+
const automationSession = options.automationSession ? automationPreviewDetails(options.automationSession) : undefined;
|
|
78
|
+
const previewDetails = makePreviewDetails(targetCapability, tradeParams, automation, automationSession);
|
|
79
|
+
const decision = evaluateSafety(targetCapability, input, policy, state);
|
|
80
|
+
if (!decision.allowed) {
|
|
81
|
+
throw new ProductError({
|
|
82
|
+
code: decision.code ?? "RCORE20002",
|
|
83
|
+
status: "BLOCKED",
|
|
84
|
+
message: decision.reason ?? "Preview blocked by safety policy."
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const previewId = `rpv_${randomUUID()}`;
|
|
88
|
+
const record = {
|
|
89
|
+
previewId,
|
|
90
|
+
capabilityId: targetCapability.capabilityId,
|
|
91
|
+
paramsHash: stableParamsHash(tradeParams),
|
|
92
|
+
...previewDetails,
|
|
93
|
+
expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
|
|
94
|
+
submitted: false,
|
|
95
|
+
status: "PASS",
|
|
96
|
+
confirmation: makeSubmitConfirmation(previewId, automation, automationSession),
|
|
97
|
+
...(automation ? { automation } : {}),
|
|
98
|
+
...(automationSession ? { automationSession } : {})
|
|
99
|
+
};
|
|
100
|
+
store.records.set(previewId, record);
|
|
101
|
+
return record;
|
|
102
|
+
}
|
|
103
|
+
export function assertTargetedTradePreviewInput(targetCapability, input) {
|
|
71
104
|
assertInputMatchesSchema(targetCapability.inputSchema, input, {
|
|
72
105
|
allowedExtraFields: [
|
|
73
106
|
"targetCapabilityId",
|
|
@@ -79,7 +112,8 @@ export function createTargetedTradePreview(targetCapability, input, policy, stat
|
|
|
79
112
|
"skillsSchemaVersion",
|
|
80
113
|
"automationMode",
|
|
81
114
|
"automationConsentText",
|
|
82
|
-
"automationScope"
|
|
115
|
+
"automationScope",
|
|
116
|
+
"automationSessionId"
|
|
83
117
|
],
|
|
84
118
|
ignoredRequiredFields: ["previewId", "continueConsentId"]
|
|
85
119
|
});
|
|
@@ -97,39 +131,16 @@ export function createTargetedTradePreview(targetCapability, input, policy, stat
|
|
|
97
131
|
message: "trade preview target cannot be another preview capability."
|
|
98
132
|
});
|
|
99
133
|
}
|
|
100
|
-
const tradeParams = normalizeTradeParams(input);
|
|
101
|
-
const automation = makeAutomationDetails(input, tradeParams);
|
|
102
|
-
const previewDetails = makePreviewDetails(targetCapability, tradeParams, automation);
|
|
103
|
-
const decision = evaluateSafety(targetCapability, input, policy, state);
|
|
104
|
-
if (!decision.allowed) {
|
|
105
|
-
throw new ProductError({
|
|
106
|
-
code: decision.code ?? "RCORE20002",
|
|
107
|
-
status: "BLOCKED",
|
|
108
|
-
message: decision.reason ?? "Preview blocked by safety policy."
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
const previewId = `rpv_${randomUUID()}`;
|
|
112
|
-
const record = {
|
|
113
|
-
previewId,
|
|
114
|
-
capabilityId: targetCapability.capabilityId,
|
|
115
|
-
paramsHash: stableParamsHash(tradeParams),
|
|
116
|
-
...previewDetails,
|
|
117
|
-
expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
|
|
118
|
-
submitted: false,
|
|
119
|
-
status: "PASS",
|
|
120
|
-
confirmation: makeSubmitConfirmation(previewId, automation),
|
|
121
|
-
...(automation ? { automation } : {})
|
|
122
|
-
};
|
|
123
|
-
store.records.set(previewId, record);
|
|
124
|
-
return record;
|
|
125
134
|
}
|
|
126
|
-
function makeSubmitConfirmation(previewId, automation) {
|
|
135
|
+
function makeSubmitConfirmation(previewId, automation, automationSession) {
|
|
127
136
|
return {
|
|
128
137
|
submitToken: `confirm_${previewId}`,
|
|
129
138
|
requiredFields: ["previewId", "continueConsentId"],
|
|
130
|
-
instruction:
|
|
131
|
-
? "
|
|
132
|
-
:
|
|
139
|
+
instruction: automationSession
|
|
140
|
+
? "This preview is authorized by an active automation session. The agent may pass submitToken as continueConsentId without asking for another per-order chat confirmation, within the session scope."
|
|
141
|
+
: automation
|
|
142
|
+
? "Automation mode is enabled for this preview. The agent may pass submitToken as continueConsentId without asking for another per-order chat confirmation, within the authorized scope."
|
|
143
|
+
: "Pass submitToken as continueConsentId only after the user has confirmed this exact preview."
|
|
133
144
|
};
|
|
134
145
|
}
|
|
135
146
|
export function verifyPreview(store, previewId, capability, input, now = new Date()) {
|
|
@@ -175,9 +186,23 @@ export function verifyPreview(store, previewId, capability, input, now = new Dat
|
|
|
175
186
|
}
|
|
176
187
|
export function consumePreview(store, previewId, capability, input, now = new Date()) {
|
|
177
188
|
const record = verifyPreview(store, previewId, capability, input, now);
|
|
189
|
+
assertSubmitToken(record, input);
|
|
178
190
|
store.records.delete(record.previewId);
|
|
179
191
|
return record;
|
|
180
192
|
}
|
|
193
|
+
function assertSubmitToken(record, input) {
|
|
194
|
+
const expected = record.confirmation?.submitToken;
|
|
195
|
+
if (!expected) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (input.continueConsentId !== expected) {
|
|
199
|
+
throw new ProductError({
|
|
200
|
+
code: "RCORE20002",
|
|
201
|
+
status: "BLOCKED",
|
|
202
|
+
message: "continueConsentId must match the preview confirmation.submitToken."
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
181
206
|
function normalizeTradeParams(input) {
|
|
182
207
|
const normalized = {};
|
|
183
208
|
for (const [key, value] of Object.entries(input)) {
|
|
@@ -195,11 +220,11 @@ function canonicalSymbolOrOriginal(value) {
|
|
|
195
220
|
return value;
|
|
196
221
|
}
|
|
197
222
|
}
|
|
198
|
-
function makePreviewDetails(capability, tradeParams, automation) {
|
|
223
|
+
function makePreviewDetails(capability, tradeParams, automation, automationSession) {
|
|
199
224
|
return {
|
|
200
225
|
businessParams: { ...tradeParams },
|
|
201
226
|
requestSummary: makeRequestSummary(capability.capabilityId, tradeParams),
|
|
202
|
-
riskNotes: makeRiskNotes(capability.capabilityId, tradeParams, automation)
|
|
227
|
+
riskNotes: makeRiskNotes(capability.capabilityId, tradeParams, automation, automationSession)
|
|
203
228
|
};
|
|
204
229
|
}
|
|
205
230
|
function makeRequestSummary(capabilityId, params) {
|
|
@@ -259,8 +284,11 @@ function makeRequestSummary(capabilityId, params) {
|
|
|
259
284
|
}
|
|
260
285
|
return compactRecord({ action: capabilityId, ...params });
|
|
261
286
|
}
|
|
262
|
-
function makeRiskNotes(capabilityId, params, automation) {
|
|
287
|
+
function makeRiskNotes(capabilityId, params, automation, automationSession) {
|
|
263
288
|
const notes = [];
|
|
289
|
+
if (automationSession) {
|
|
290
|
+
notes.push("Automation session allows the agent to submit this preview with submitToken without asking for another per-order chat confirmation.");
|
|
291
|
+
}
|
|
264
292
|
if (automation) {
|
|
265
293
|
notes.push("Automation mode allows the agent to submit this preview with submitToken without asking for another per-order chat confirmation.");
|
|
266
294
|
}
|
|
@@ -388,7 +416,8 @@ const NON_TRADE_HASH_FIELDS = new Set([
|
|
|
388
416
|
"skillsSchemaVersion",
|
|
389
417
|
"automationMode",
|
|
390
418
|
"automationConsentText",
|
|
391
|
-
"automationScope"
|
|
419
|
+
"automationScope",
|
|
420
|
+
"automationSessionId"
|
|
392
421
|
]);
|
|
393
422
|
function isTradePreviewRecord(value) {
|
|
394
423
|
if (!value || typeof value !== "object") {
|
package/dist/core/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const RAPIDX_VERSION = "1.0.
|
|
1
|
+
export const RAPIDX_VERSION = "1.0.36";
|
|
@@ -29,6 +29,11 @@ const TOOL_DESCRIPTIONS = {
|
|
|
29
29
|
"schema.discover": `Discover the RapidX MCP tool surface, capability metadata, and concrete JSON input schemas. Use this before constructing tool inputs. Schema version: ${SCHEMA_VERSION}.`,
|
|
30
30
|
"self-check.run": "Run a read-only RapidX integration self-check. Verifies tool discovery, schema, credential configuration, and optional live read probes without submitting trade writes.",
|
|
31
31
|
"update.check": "Check the RapidX release manifest for CLI, MCP schema, and skills versions. Use during setup, review, or session startup; do not run before every trade submit.",
|
|
32
|
+
"automation.start": "Create a bounded local RapidX automation session for agent trading. The user must authorize symbols, per-order notional, total notional, duration, allowed actions, and allowed order types before this tool is called. Use order.place/order.replace/order.cancel for normal order lifecycle automation.",
|
|
33
|
+
"automation.list": "List local RapidX automation sessions in the current workspace, including active, stopped, and expired sessions.",
|
|
34
|
+
"automation.status": "Read one RapidX automation session by automationSessionId, including remaining scope, usedNotional, and expiration.",
|
|
35
|
+
"automation.extend": "Extend an active RapidX automation session after the user authorizes a new duration. This cannot expand symbols, actions, order types, or notional limits.",
|
|
36
|
+
"automation.stop": "Stop a RapidX automation session. Stopping blocks future automation previews and submits for that session; it does not cancel existing orders.",
|
|
32
37
|
"market.ticker": "Read the latest ticker for a RapidX symbol. Public market adapter may query Binance or OKX venue APIs and returns canonical RapidX symbol plus original venue symbol when they differ.",
|
|
33
38
|
"market.orderbook": "Read the order book for a RapidX symbol. Public market adapter may query Binance or OKX venue depth/books endpoints and returns canonical RapidX symbol plus venue data.",
|
|
34
39
|
"market.klines": "Read historical candlesticks for a RapidX symbol. Requires symbol and interval, with optional limit. Public market adapter currently supports Binance-style kline data.",
|
|
@@ -43,9 +48,9 @@ const TOOL_DESCRIPTIONS = {
|
|
|
43
48
|
"portfolio.position-bracket": "Read RapidX position tier/bracket information for a symbol from GET /api/v1/trading/positionBracket.",
|
|
44
49
|
"portfolio.set-position-mode": "Preview-required write: change RapidX account position mode through POST /api/v1/trading/account. Use only when the user explicitly asks to change account position mode.",
|
|
45
50
|
"trade.preview": "Create a generic preview token by targetCapabilityId for write operations without a dedicated order preview command, including portfolio writes, position writes, and algo writes before submitting the target tool.",
|
|
46
|
-
"order.place-preview": "Preview an order placement before submitting a real order. Validates input schema, local safety rules, symbol trading rules from GET /api/v1/trading/sym/info, maxNotional, and returns previewId plus confirmation.submitToken.",
|
|
47
|
-
"order.replace-preview": "Preview an order replacement before submitting a real replace. Performs order readback through GET /api/v1/trading/order to verify the order exists and is replaceable, then returns previewId plus confirmation.submitToken.",
|
|
48
|
-
"order.cancel-preview": "Preview an order cancellation before submitting a real cancel. Performs order readback through GET /api/v1/trading/order to verify the order exists and is cancelable, then returns previewId plus confirmation.submitToken.",
|
|
51
|
+
"order.place-preview": "Preview an order placement before submitting a real order. Validates input schema, local safety rules, optional automationSessionId scope, symbol trading rules from GET /api/v1/trading/sym/info, maxNotional, and returns previewId plus confirmation.submitToken.",
|
|
52
|
+
"order.replace-preview": "Preview an order replacement before submitting a real replace. Performs order readback through GET /api/v1/trading/order to verify the order exists and is replaceable, checks optional automationSessionId scope, then returns previewId plus confirmation.submitToken. Automated replace consumes replacement order notional.",
|
|
53
|
+
"order.cancel-preview": "Preview an order cancellation before submitting a real cancel. Performs order readback through GET /api/v1/trading/order to verify the order exists and is cancelable, checks optional automationSessionId scope, then returns previewId plus confirmation.submitToken. Automated cancel consumes no notional.",
|
|
49
54
|
"order.place": "Preview-required write: submit a real order to POST /api/v1/trading/order. Must use unchanged business parameters from order.place-preview and continueConsentId=confirmation.submitToken.",
|
|
50
55
|
"order.replace": "Preview-required write: replace a real order through PUT /api/v1/trading/order. Must use unchanged business parameters from order.replace-preview or a matching trade preview.",
|
|
51
56
|
"order.cancel": "Preview-required write: cancel a real order through DELETE /api/v1/trading/order. Cancel can be asynchronous; inspect cancelAccepted, terminalStateConfirmed, and poll order.query when needed.",
|
package/dist/mcp/tool-runner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, getSchemaResult, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, publicErrorDetails, runSelfCheck, runPreviewPreflight, runTradingVerification, verifyPreview, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
|
|
1
|
+
import { consumePreview, createTargetedTradePreview, createTradePreview, defaultSafetyPolicy, executeRapidXCapability, findCapabilityById, assertAutomationSessionStillAllowsSubmit, assertTargetedTradePreviewInput, extendAutomationSession, getAutomationSession, getSchemaResult, listAutomationSessions, loadAutomationSessionStoreFromFile, makeEvidence, makePreviewStore, makeSafetyState, normalizeUnknownError, ProductError, publicErrorDetails, recordAutomationSessionSubmit, resolveAutomationSessionForPreview, resolveAutomationSessionStoreFile, runSelfCheck, runPreviewPreflight, runTradingVerification, verifyPreview, saveAutomationSessionStoreToFile, startAutomationSession, stopAutomationSession, withAutomationSessionStoreLock, buildLiveReadOnlySelfCheck, buildLiveTradingVerificationProbes, checkForUpdate, } from "../core/index.js";
|
|
2
2
|
import { capabilityForTool, getMcpToolDefinitions } from "./tool-registry.js";
|
|
3
3
|
import { writeMcpAudit } from "./audit.js";
|
|
4
4
|
const previewStore = makePreviewStore();
|
|
@@ -48,6 +48,9 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
48
48
|
const auditId = writeMcpAudit("update-check", status, { toolName, updateStatus: update.status });
|
|
49
49
|
return { ok: status === "PASS", status, data: update, auditId, evidence: [makeEvidence(toolName)] };
|
|
50
50
|
}
|
|
51
|
+
if (toolName.startsWith("rapidx/automation/")) {
|
|
52
|
+
return runAutomationTool(toolName, input);
|
|
53
|
+
}
|
|
51
54
|
if (toolName === "rapidx/trading-verification" || toolName === "rapidx/trade/verify-live") {
|
|
52
55
|
const report = await runTradingVerification(input, buildLiveTradingVerificationProbes(input));
|
|
53
56
|
const auditId = writeMcpAudit("trading-verification", report.status, { toolName, status: report.status });
|
|
@@ -59,7 +62,9 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
59
62
|
if (!previewCapability) {
|
|
60
63
|
throw new Error("order.place-preview capability missing");
|
|
61
64
|
}
|
|
62
|
-
const
|
|
65
|
+
const automationStore = loadAutomationSessionStoreFromFile(resolveAutomationSessionStoreFile());
|
|
66
|
+
const automation = optionalAutomationSession(automationStore, "order.place", input);
|
|
67
|
+
const preview = createTradePreview(previewCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore, 300, automation ? { automationSession: automation.session } : {});
|
|
63
68
|
try {
|
|
64
69
|
Object.assign(preview, await runPreviewPreflight("order.place", input));
|
|
65
70
|
}
|
|
@@ -76,9 +81,13 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
76
81
|
if (!targetCapability) {
|
|
77
82
|
throw new Error(`${concretePreviewTarget} capability missing`);
|
|
78
83
|
}
|
|
79
|
-
|
|
84
|
+
assertTargetedTradePreviewInput(targetCapability, input);
|
|
85
|
+
const preflight = await runPreviewPreflight(concretePreviewTarget, input);
|
|
86
|
+
const automationStore = loadAutomationSessionStoreFromFile(resolveAutomationSessionStoreFile());
|
|
87
|
+
const automation = optionalAutomationSession(automationStore, concretePreviewTarget, { ...input, ...preflight });
|
|
88
|
+
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore, 300, automation ? { automationSession: automation.session } : {});
|
|
80
89
|
try {
|
|
81
|
-
Object.assign(preview,
|
|
90
|
+
Object.assign(preview, preflight);
|
|
82
91
|
}
|
|
83
92
|
catch (error) {
|
|
84
93
|
previewStore.records.delete(preview.previewId);
|
|
@@ -96,9 +105,13 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
96
105
|
if (targetCapability.operationType !== "TRADE_WRITE" || isPreviewCapability(targetCapability.capabilityId)) {
|
|
97
106
|
throw new Error("trade preview target must be a non-preview trade write capability.");
|
|
98
107
|
}
|
|
99
|
-
|
|
108
|
+
assertTargetedTradePreviewInput(targetCapability, input);
|
|
109
|
+
const preflight = await runPreviewPreflight(targetCapability.capabilityId, input);
|
|
110
|
+
const automationStore = loadAutomationSessionStoreFromFile(resolveAutomationSessionStoreFile());
|
|
111
|
+
const automation = optionalAutomationSession(automationStore, targetCapability.capabilityId, { ...input, ...preflight });
|
|
112
|
+
const preview = createTargetedTradePreview(targetCapability, input, { ...defaultSafetyPolicy(), tradingEnabled: true, readOnly: false }, safetyState, previewStore, 300, automation ? { automationSession: automation.session } : {});
|
|
100
113
|
try {
|
|
101
|
-
Object.assign(preview,
|
|
114
|
+
Object.assign(preview, preflight);
|
|
102
115
|
}
|
|
103
116
|
catch (error) {
|
|
104
117
|
previewStore.records.delete(preview.previewId);
|
|
@@ -107,15 +120,45 @@ export async function runMcpTool(toolName, input = {}) {
|
|
|
107
120
|
const auditId = writeMcpAudit("trade-preview", "PASS", { toolName, capabilityId: targetCapability.capabilityId, previewId: preview.previewId });
|
|
108
121
|
return { ok: true, status: "PASS", data: preview, auditId, evidence: [makeEvidence(toolName)] };
|
|
109
122
|
}
|
|
123
|
+
let consumedPreview;
|
|
110
124
|
if (capability.previewRequired) {
|
|
111
|
-
|
|
125
|
+
const automationStore = loadAutomationSessionStoreFromFile(resolveAutomationSessionStoreFile());
|
|
126
|
+
const previewRecord = verifyPreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
127
|
+
assertAutomationSessionStillAllowsSubmit(automationStore, previewRecord, capability.capabilityId);
|
|
112
128
|
const consentId = input.continueConsentId;
|
|
113
129
|
if (typeof consentId !== "string" || consentId.length === 0) {
|
|
114
130
|
return { ok: false, status: "BLOCKED", code: "RMCP20001", message: "continueConsentId is required for trade writes.", evidence: [makeEvidence(toolName)] };
|
|
115
131
|
}
|
|
116
|
-
|
|
132
|
+
if (previewRecord.automationSession) {
|
|
133
|
+
const automationStoreFile = resolveAutomationSessionStoreFile();
|
|
134
|
+
const data = await withAutomationSessionStoreLock(automationStoreFile, async () => {
|
|
135
|
+
const lockedAutomationStore = loadAutomationSessionStoreFromFile(automationStoreFile);
|
|
136
|
+
const lockedPreviewRecord = verifyPreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
137
|
+
assertAutomationSessionStillAllowsSubmit(lockedAutomationStore, lockedPreviewRecord, capability.capabilityId);
|
|
138
|
+
const lockedConsumedPreview = consumePreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
139
|
+
const result = await executeRapidXCapability(capability.capabilityId, input);
|
|
140
|
+
recordAutomationSessionSubmit(lockedAutomationStore, lockedConsumedPreview, new Date(), capability.capabilityId);
|
|
141
|
+
saveAutomationSessionStoreToFile(automationStoreFile, lockedAutomationStore);
|
|
142
|
+
return result;
|
|
143
|
+
});
|
|
144
|
+
const auditId = writeMcpAudit("tool-call", "PASS", { toolName, capabilityId: capability.capabilityId });
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
status: "PASS",
|
|
148
|
+
data,
|
|
149
|
+
auditId,
|
|
150
|
+
evidence: [makeEvidence(toolName, "real_tool_call")]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
consumedPreview = consumePreview(previewStore, typeof input.previewId === "string" ? input.previewId : undefined, capability, input);
|
|
117
154
|
}
|
|
118
155
|
const data = await executeRapidXCapability(capability.capabilityId, input);
|
|
156
|
+
if (consumedPreview?.automationSession) {
|
|
157
|
+
const automationStoreFile = resolveAutomationSessionStoreFile();
|
|
158
|
+
const automationStore = loadAutomationSessionStoreFromFile(automationStoreFile);
|
|
159
|
+
recordAutomationSessionSubmit(automationStore, consumedPreview, new Date(), capability.capabilityId);
|
|
160
|
+
saveAutomationSessionStoreToFile(automationStoreFile, automationStore);
|
|
161
|
+
}
|
|
119
162
|
const auditId = writeMcpAudit("tool-call", "PASS", { toolName, capabilityId: capability.capabilityId });
|
|
120
163
|
return {
|
|
121
164
|
ok: true,
|
|
@@ -147,6 +190,46 @@ function concretePreviewTargetForTool(toolName) {
|
|
|
147
190
|
}
|
|
148
191
|
return undefined;
|
|
149
192
|
}
|
|
193
|
+
function runAutomationTool(toolName, input) {
|
|
194
|
+
const action = toolName.split("/").at(-1) ?? "";
|
|
195
|
+
const filePath = resolveAutomationSessionStoreFile();
|
|
196
|
+
return withAutomationSessionStoreLock(filePath, () => {
|
|
197
|
+
const store = loadAutomationSessionStoreFromFile(filePath);
|
|
198
|
+
if (action === "start") {
|
|
199
|
+
const session = startAutomationSession(store, input);
|
|
200
|
+
saveAutomationSessionStoreToFile(filePath, store);
|
|
201
|
+
return { ok: true, status: "PASS", data: session, evidence: [makeEvidence(toolName)] };
|
|
202
|
+
}
|
|
203
|
+
if (action === "list") {
|
|
204
|
+
return { ok: true, status: "PASS", data: { sessions: listAutomationSessions(store) }, evidence: [makeEvidence(toolName)] };
|
|
205
|
+
}
|
|
206
|
+
if (action === "status") {
|
|
207
|
+
return { ok: true, status: "PASS", data: getAutomationSession(store, input), evidence: [makeEvidence(toolName)] };
|
|
208
|
+
}
|
|
209
|
+
if (action === "extend") {
|
|
210
|
+
const session = extendAutomationSession(store, input);
|
|
211
|
+
saveAutomationSessionStoreToFile(filePath, store);
|
|
212
|
+
return { ok: true, status: "PASS", data: session, evidence: [makeEvidence(toolName)] };
|
|
213
|
+
}
|
|
214
|
+
if (action === "stop") {
|
|
215
|
+
const session = stopAutomationSession(store, input);
|
|
216
|
+
saveAutomationSessionStoreToFile(filePath, store);
|
|
217
|
+
return { ok: true, status: "PASS", data: session, evidence: [makeEvidence(toolName)] };
|
|
218
|
+
}
|
|
219
|
+
throw new Error(`Unknown automation tool: ${toolName}`);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
function optionalAutomationSession(store, targetCapabilityId, input) {
|
|
223
|
+
try {
|
|
224
|
+
return resolveAutomationSessionForPreview(store, targetCapabilityId, input);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
if (error instanceof ProductError && error.code === "RCORE26001") {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
150
233
|
function tradingVerificationEnvelope(toolName, report, auditId) {
|
|
151
234
|
if (report.status === "PASS") {
|
|
152
235
|
return { ok: true, status: "PASS", data: report, auditId, evidence: [makeEvidence(toolName)] };
|
package/package.json
CHANGED
|
@@ -48,13 +48,13 @@ rapidx portfolio set-position-mode --input @/absolute/path/set-position-mode.jso
|
|
|
48
48
|
When checking an existing installation, read the installed RapidX skill `version` from the loaded `SKILL.md` frontmatter and pass it to update check:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
rapidx update check --input '{"installedSkillsVersion":"1.0.
|
|
51
|
+
rapidx update check --input '{"installedSkillsVersion":"1.0.10"}' --json
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
For upgrade reviews, force a remote manifest refresh:
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
rapidx update check --input '{"installedSkillsVersion":"1.0.
|
|
57
|
+
rapidx update check --input '{"installedSkillsVersion":"1.0.10","force":true}' --json
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Update output fields:
|
|
@@ -84,7 +84,6 @@ When using `orderId`, pass the RapidX 16-digit numeric order id. Invalid `orderI
|
|
|
84
84
|
|
|
85
85
|
`rapidx transaction executions` reads transaction executions from `GET /api/v1/trading/executions`.
|
|
86
86
|
|
|
87
|
-
Automation
|
|
88
|
-
Valid automation preview returns an `automation` object with `confirmationMode="automation-preview"`; generic or missing automation consent text is blocked before submission.
|
|
87
|
+
Automation session still uses preview. Create a bounded session with `rapidx automation start` only after the user authorizes the scope; the input must include `explicitUserConsent=true` and `acceptedRiskText`. Pass `automationSessionId` to order place/replace/cancel preview, then submit the returned `previewId` and `confirmation.submitToken` without another per-order chat confirmation. Use `rapidx automation extend` only after the user authorizes more time and include a new `acceptedRiskText`; use `rapidx automation stop` when the user stops automation. For order lifecycle automation, include `allowedActions:["order.place","order.replace","order.cancel"]`; place consumes notional by `maxNotional`, replace consumes the replacement order notional, and cancel consumes no notional.
|
|
89
88
|
|
|
90
89
|
`rapidx position close` is a close-position action. Do not pass `side` or `quantity`; RapidX determines BUY or SELL from the current position and closes the target symbol/positionSide. Use a reduce-only order flow for partial closes. Verify the final exposure with `rapidx position query --json`, and do not rely only on `order query` to interpret the close intent.
|
|
@@ -31,8 +31,8 @@ Key trading tools:
|
|
|
31
31
|
- `rapidx/trade/preview` creates generic preview tokens by `targetCapabilityId` for write operations without a dedicated order preview command, including position, portfolio, and algo writes.
|
|
32
32
|
- Preview ids are local to the running MCP server. Do not submit a CLI-created preview id through MCP, and do not submit an MCP-created preview id through CLI.
|
|
33
33
|
- `rapidx/order/cancel` returns cancel acceptance plus terminal-state guidance. Poll `rapidx/order/query` when `terminalStateConfirmed=false`.
|
|
34
|
-
- Automation
|
|
35
|
-
-
|
|
34
|
+
- Automation session still uses preview. Create a bounded session with `rapidx/automation/start` only after the user authorizes the scope; the input must include `explicitUserConsent=true` and `acceptedRiskText`. Pass `automationSessionId` to order place/replace/cancel preview, then submit the returned `previewId` and `confirmation.submitToken` without another per-order chat confirmation. Include `allowedActions:["order.place","order.replace","order.cancel"]` when the automation should manage an order lifecycle.
|
|
35
|
+
- Use `rapidx/automation/status`, `rapidx/automation/extend`, and `rapidx/automation/stop` for session lifecycle management. `rapidx/automation/extend` also requires `explicitUserConsent=true` and a new `acceptedRiskText`.
|
|
36
36
|
- `rapidx/position/history` reads historical positions.
|
|
37
37
|
- `rapidx/order/history` and `rapidx/algo/history` accept optional `begin` and `end` time range fields in milliseconds. If omitted, RapidX applies the upstream server default range.
|
|
38
38
|
- `rapidx/transaction/executions` reads transaction executions from `GET /api/v1/trading/executions`.
|
|
@@ -130,7 +130,13 @@ Preview ids are runtime-local. Submit MCP previews through the same MCP server r
|
|
|
130
130
|
|
|
131
131
|
`order.cancel` is asynchronous. If the cancel result says `terminalStateConfirmed=false`, poll `order query` until a terminal state before claiming that the order is cancelled.
|
|
132
132
|
|
|
133
|
-
For automation, keep the preview-first flow.
|
|
133
|
+
For automation, keep the preview-first flow. Create a bounded automation session first, then pass `automationSessionId` into order place/replace/cancel preview input. If the preview returns `automationSession.confirmationMode="automation-session"` and `confirmation.submitToken`, submit that preview without another per-order chat confirmation.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
rapidx automation start --input '{"symbols":["BINANCE_PERP_BTC_USDT"],"maxNotionalPerOrder":"100","maxTotalNotional":"1000","expiresInSeconds":3600,"allowedActions":["order.place","order.replace","order.cancel"],"allowedOrderTypes":["MARKET","LIMIT"],"explicitUserConsent":true,"acceptedRiskText":"I authorize RapidX automation for BINANCE_PERP_BTC_USDT with maxNotionalPerOrder 100 and maxTotalNotional 1000."}' --json
|
|
137
|
+
rapidx order place-preview --input '{"automationSessionId":"<automationSessionId>","symbol":"BINANCE_PERP_BTC_USDT","side":"BUY","orderType":"MARKET","amount":"50","maxNotional":"60","clientOrderId":"auto-001"}' --json
|
|
138
|
+
rapidx order place --input '{"symbol":"BINANCE_PERP_BTC_USDT","side":"BUY","orderType":"MARKET","amount":"50","maxNotional":"60","clientOrderId":"auto-001","previewId":"<previewId>","continueConsentId":"<confirmation.submitToken>"}' --json
|
|
139
|
+
```
|
|
134
140
|
|
|
135
141
|
`maxNotional` is a safety upper bound, not the target order amount. If a requested amount is below the symbol `minNotional`, ask the user to confirm the larger amount before submitting. For `position.close`, do not pass `side` or `quantity`; RapidX determines the close side from the current position and closes the target symbol/positionSide. Use a reduce-only order flow for partial closes, then check final exposure with `position query`.
|
|
136
142
|
|