@michaleffffff/mcp-trading-server 3.1.1 → 3.1.2
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/CHANGELOG.md +6 -0
- package/dist/services/tradeService.js +69 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.1.2 - 2026-03-23
|
|
4
|
+
### Changed
|
|
5
|
+
- Added local preflight validation for opening `LIMIT` / `STOP` orders so `open_position_simple` and `execute_trade` now fail fast with clear guidance when:
|
|
6
|
+
- `triggerType` conflicts with the selected open-order semantics
|
|
7
|
+
- the target price is on the wrong side of the current oracle price for the chosen `LONG` / `SHORT` + `LIMIT` / `STOP` combination
|
|
8
|
+
|
|
3
9
|
## 3.1.1 - 2026-03-23
|
|
4
10
|
### Changed
|
|
5
11
|
- Upgraded the active SDK baseline to `@myx-trade/sdk@^1.0.4`.
|
|
@@ -121,6 +121,68 @@ function computeRecommendedSizeRaw(targetQuoteRaw, priceRaw30, baseDecimals, quo
|
|
|
121
121
|
const denominator = priceRaw30 * (10n ** BigInt(quoteDecimals));
|
|
122
122
|
return numerator / denominator;
|
|
123
123
|
}
|
|
124
|
+
function getOrderTypeLabel(orderType) {
|
|
125
|
+
if (orderType === OrderType.MARKET)
|
|
126
|
+
return "MARKET";
|
|
127
|
+
if (orderType === OrderType.LIMIT)
|
|
128
|
+
return "LIMIT";
|
|
129
|
+
if (orderType === OrderType.STOP)
|
|
130
|
+
return "STOP";
|
|
131
|
+
return `ORDER_TYPE_${String(orderType)}`;
|
|
132
|
+
}
|
|
133
|
+
function getDirectionLabel(direction) {
|
|
134
|
+
return direction === 0 ? "LONG" : "SHORT";
|
|
135
|
+
}
|
|
136
|
+
function getTriggerTypeLabel(triggerType) {
|
|
137
|
+
if (triggerType === TriggerType.NONE)
|
|
138
|
+
return "NONE";
|
|
139
|
+
if (triggerType === TriggerType.GTE)
|
|
140
|
+
return "GTE";
|
|
141
|
+
if (triggerType === TriggerType.LTE)
|
|
142
|
+
return "LTE";
|
|
143
|
+
return `TRIGGER_TYPE_${String(triggerType)}`;
|
|
144
|
+
}
|
|
145
|
+
async function validateIncreaseOrderTriggerSemantics(client, args, chainId) {
|
|
146
|
+
const directionIndex = resolveDirectionIndex(args.direction);
|
|
147
|
+
const orderType = Number(args.orderType);
|
|
148
|
+
const explicitTriggerType = args.triggerType;
|
|
149
|
+
if (orderType === OrderType.MARKET) {
|
|
150
|
+
if (explicitTriggerType !== undefined && explicitTriggerType !== null && Number(explicitTriggerType) !== TriggerType.NONE) {
|
|
151
|
+
throw new Error("Invalid triggerType for MARKET open order: MARKET orders must use triggerType=0/NONE.");
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (orderType !== OrderType.LIMIT && orderType !== OrderType.STOP) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const expectedTriggerType = resolveTriggerType(orderType, directionIndex, false);
|
|
159
|
+
const effectiveTriggerType = explicitTriggerType === undefined || explicitTriggerType === null
|
|
160
|
+
? expectedTriggerType
|
|
161
|
+
: Number(explicitTriggerType);
|
|
162
|
+
if (effectiveTriggerType !== expectedTriggerType) {
|
|
163
|
+
throw new Error(`Invalid triggerType for opening ${getDirectionLabel(directionIndex)} ${getOrderTypeLabel(orderType)} order: ` +
|
|
164
|
+
`expected ${getTriggerTypeLabel(expectedTriggerType)}, got ${getTriggerTypeLabel(effectiveTriggerType)}.`);
|
|
165
|
+
}
|
|
166
|
+
const oracleData = await getFreshOraclePrice(client, args.poolId, chainId);
|
|
167
|
+
const currentPriceRaw = ensureUnits(oracleData.price, 30, "oracle price", { allowImplicitRaw: false });
|
|
168
|
+
const targetPriceRaw = BigInt(args.priceRaw30);
|
|
169
|
+
const currentPriceRawBig = BigInt(currentPriceRaw);
|
|
170
|
+
const currentPriceHuman = formatUnits(currentPriceRawBig, 30);
|
|
171
|
+
const targetPriceHuman = formatUnits(targetPriceRaw, 30);
|
|
172
|
+
const orderLabel = `${getDirectionLabel(directionIndex)} ${getOrderTypeLabel(orderType)}`;
|
|
173
|
+
const shouldBeBelowCurrent = (orderType === OrderType.LIMIT && directionIndex === 0) ||
|
|
174
|
+
(orderType === OrderType.STOP && directionIndex === 1);
|
|
175
|
+
const shouldBeAboveCurrent = (orderType === OrderType.STOP && directionIndex === 0) ||
|
|
176
|
+
(orderType === OrderType.LIMIT && directionIndex === 1);
|
|
177
|
+
if (shouldBeBelowCurrent && targetPriceRaw >= currentPriceRawBig) {
|
|
178
|
+
throw new Error(`Invalid ${orderLabel} price: target price ${targetPriceHuman} must be below current oracle price ${currentPriceHuman}. ` +
|
|
179
|
+
`If you want to trade above current price, use ${directionIndex === 0 ? "STOP LONG" : "LIMIT SHORT"} instead.`);
|
|
180
|
+
}
|
|
181
|
+
if (shouldBeAboveCurrent && targetPriceRaw <= currentPriceRawBig) {
|
|
182
|
+
throw new Error(`Invalid ${orderLabel} price: target price ${targetPriceHuman} must be above current oracle price ${currentPriceHuman}. ` +
|
|
183
|
+
`If you want to trade below current price, use ${directionIndex === 0 ? "LIMIT LONG" : "STOP SHORT"} instead.`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
124
186
|
function validateIncreaseOrderEconomics(args) {
|
|
125
187
|
const collateralRawBig = BigInt(args.collateralRaw);
|
|
126
188
|
const sizeRawBig = BigInt(args.sizeRaw);
|
|
@@ -250,6 +312,13 @@ export async function openPosition(client, address, args) {
|
|
|
250
312
|
baseDecimals,
|
|
251
313
|
quoteDecimals,
|
|
252
314
|
});
|
|
315
|
+
await validateIncreaseOrderTriggerSemantics(client, {
|
|
316
|
+
poolId: args.poolId,
|
|
317
|
+
orderType: Number(args.orderType),
|
|
318
|
+
direction: args.direction,
|
|
319
|
+
triggerType: args.triggerType,
|
|
320
|
+
priceRaw30: priceRaw,
|
|
321
|
+
}, chainId);
|
|
253
322
|
const timeInForce = mapTimeInForce(args.timeInForce);
|
|
254
323
|
const orderParams = {
|
|
255
324
|
chainId,
|