@guiie/buda-mcp 1.3.0 → 1.4.1
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 +41 -0
- package/PUBLISH_CHECKLIST.md +69 -70
- package/README.md +4 -4
- package/dist/http.js +17 -0
- package/dist/index.js +10 -0
- package/dist/tools/calculate_position_size.d.ts +48 -0
- package/dist/tools/calculate_position_size.d.ts.map +1 -0
- package/dist/tools/calculate_position_size.js +111 -0
- package/dist/tools/dead_mans_switch.d.ts +84 -0
- package/dist/tools/dead_mans_switch.d.ts.map +1 -0
- package/dist/tools/dead_mans_switch.js +236 -0
- package/dist/tools/market_sentiment.d.ts +30 -0
- package/dist/tools/market_sentiment.d.ts.map +1 -0
- package/dist/tools/market_sentiment.js +104 -0
- package/dist/tools/price_history.d.ts.map +1 -1
- package/dist/tools/price_history.js +2 -40
- package/dist/tools/simulate_order.d.ts +45 -0
- package/dist/tools/simulate_order.d.ts.map +1 -0
- package/dist/tools/simulate_order.js +139 -0
- package/dist/tools/technical_indicators.d.ts +39 -0
- package/dist/tools/technical_indicators.d.ts.map +1 -0
- package/dist/tools/technical_indicators.js +223 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +7 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +47 -0
- package/marketplace/README.md +1 -1
- package/marketplace/claude-listing.md +35 -1
- package/marketplace/gemini-tools.json +230 -1
- package/marketplace/openapi.yaml +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/http.ts +17 -0
- package/src/index.ts +10 -0
- package/src/tools/calculate_position_size.ts +141 -0
- package/src/tools/dead_mans_switch.ts +314 -0
- package/src/tools/market_sentiment.ts +141 -0
- package/src/tools/price_history.ts +2 -54
- package/src/tools/simulate_order.ts +182 -0
- package/src/tools/technical_indicators.ts +282 -0
- package/src/types.ts +12 -0
- package/src/utils.ts +53 -1
- package/test/run-all.ts +197 -0
- package/test/unit.ts +505 -1
package/test/run-all.ts
CHANGED
|
@@ -18,6 +18,12 @@ try {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
import { BudaClient } from "../src/client.js";
|
|
21
|
+
import { MemoryCache } from "../src/cache.js";
|
|
22
|
+
import { handleSimulateOrder } from "../src/tools/simulate_order.js";
|
|
23
|
+
import { handleCalculatePositionSize } from "../src/tools/calculate_position_size.js";
|
|
24
|
+
import { handleMarketSentiment } from "../src/tools/market_sentiment.js";
|
|
25
|
+
import { handleTechnicalIndicators } from "../src/tools/technical_indicators.js";
|
|
26
|
+
import { handleScheduleCancelAll, handleDisarmCancelTimer } from "../src/tools/dead_mans_switch.js";
|
|
21
27
|
import type {
|
|
22
28
|
MarketsResponse,
|
|
23
29
|
TickerResponse,
|
|
@@ -225,6 +231,167 @@ try {
|
|
|
225
231
|
failures++;
|
|
226
232
|
}
|
|
227
233
|
|
|
234
|
+
// ----------------------------------------------------------------
|
|
235
|
+
// 9. simulate_order
|
|
236
|
+
// ----------------------------------------------------------------
|
|
237
|
+
section(`simulate_order — ${TEST_MARKET} market buy`);
|
|
238
|
+
{
|
|
239
|
+
const cache = new MemoryCache();
|
|
240
|
+
try {
|
|
241
|
+
const result = await handleSimulateOrder(
|
|
242
|
+
{ market_id: TEST_MARKET, side: "buy", amount: 0.001 },
|
|
243
|
+
client,
|
|
244
|
+
cache,
|
|
245
|
+
);
|
|
246
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
247
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
248
|
+
simulation: boolean;
|
|
249
|
+
estimated_fill_price: number;
|
|
250
|
+
fee_amount: number;
|
|
251
|
+
fee_rate_pct: number;
|
|
252
|
+
total_cost: number;
|
|
253
|
+
slippage_vs_mid_pct: number;
|
|
254
|
+
order_type_assumed: string;
|
|
255
|
+
};
|
|
256
|
+
if (parsed.simulation !== true) throw new Error("simulation flag must be true");
|
|
257
|
+
pass("simulation: true", "✓");
|
|
258
|
+
pass("order_type_assumed", parsed.order_type_assumed);
|
|
259
|
+
pass("estimated_fill_price", `${parsed.estimated_fill_price.toLocaleString()} CLP`);
|
|
260
|
+
pass("fee_rate_pct", `${parsed.fee_rate_pct}%`);
|
|
261
|
+
pass("fee_amount", `${parsed.fee_amount.toFixed(2)} CLP`);
|
|
262
|
+
pass("total_cost", `${parsed.total_cost.toFixed(2)} CLP`);
|
|
263
|
+
pass("slippage_vs_mid_pct", `${parsed.slippage_vs_mid_pct}%`);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
fail("simulate_order", err);
|
|
266
|
+
failures++;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ----------------------------------------------------------------
|
|
271
|
+
// 10. calculate_position_size
|
|
272
|
+
// ----------------------------------------------------------------
|
|
273
|
+
section(`calculate_position_size — ${TEST_MARKET}`);
|
|
274
|
+
{
|
|
275
|
+
// Fetch live ticker to use real entry/stop prices
|
|
276
|
+
try {
|
|
277
|
+
const tickerData = await client.get<TickerResponse>(
|
|
278
|
+
`/markets/${TEST_MARKET.toLowerCase()}/ticker`,
|
|
279
|
+
);
|
|
280
|
+
const lastPrice = parseFloat(tickerData.ticker.last_price[0]);
|
|
281
|
+
const entryPrice = lastPrice;
|
|
282
|
+
const stopLossPrice = parseFloat((lastPrice * 0.97).toFixed(0)); // 3% below entry
|
|
283
|
+
|
|
284
|
+
const result = handleCalculatePositionSize({
|
|
285
|
+
market_id: TEST_MARKET,
|
|
286
|
+
capital: 1_000_000,
|
|
287
|
+
risk_pct: 2,
|
|
288
|
+
entry_price: entryPrice,
|
|
289
|
+
stop_loss_price: stopLossPrice,
|
|
290
|
+
});
|
|
291
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
292
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
293
|
+
side: string;
|
|
294
|
+
units: number;
|
|
295
|
+
capital_at_risk: number;
|
|
296
|
+
position_value: number;
|
|
297
|
+
fee_impact: number;
|
|
298
|
+
fee_currency: string;
|
|
299
|
+
};
|
|
300
|
+
pass("side", parsed.side);
|
|
301
|
+
pass("units", `${parsed.units} BTC`);
|
|
302
|
+
pass("capital_at_risk", `${parsed.capital_at_risk.toLocaleString()} CLP`);
|
|
303
|
+
pass("position_value", `${parsed.position_value.toLocaleString()} CLP`);
|
|
304
|
+
pass("fee_impact", `${parsed.fee_impact.toFixed(2)} ${parsed.fee_currency}`);
|
|
305
|
+
} catch (err) {
|
|
306
|
+
fail("calculate_position_size", err);
|
|
307
|
+
failures++;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ----------------------------------------------------------------
|
|
312
|
+
// 11. get_market_sentiment
|
|
313
|
+
// ----------------------------------------------------------------
|
|
314
|
+
section(`get_market_sentiment — ${TEST_MARKET}`);
|
|
315
|
+
{
|
|
316
|
+
const cache = new MemoryCache();
|
|
317
|
+
try {
|
|
318
|
+
const result = await handleMarketSentiment({ market_id: TEST_MARKET }, client, cache);
|
|
319
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
320
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
321
|
+
score: number;
|
|
322
|
+
label: string;
|
|
323
|
+
component_breakdown: {
|
|
324
|
+
price_variation_24h_pct: number;
|
|
325
|
+
volume_ratio: number;
|
|
326
|
+
spread_pct: number;
|
|
327
|
+
};
|
|
328
|
+
disclaimer: string;
|
|
329
|
+
};
|
|
330
|
+
if (!["bearish", "neutral", "bullish"].includes(parsed.label)) {
|
|
331
|
+
throw new Error(`unexpected label: ${parsed.label}`);
|
|
332
|
+
}
|
|
333
|
+
if (typeof parsed.score !== "number" || parsed.score < -100 || parsed.score > 100) {
|
|
334
|
+
throw new Error(`score out of range: ${parsed.score}`);
|
|
335
|
+
}
|
|
336
|
+
pass("score", String(parsed.score));
|
|
337
|
+
pass("label", parsed.label);
|
|
338
|
+
pass("price_variation_24h_pct", `${parsed.component_breakdown.price_variation_24h_pct}%`);
|
|
339
|
+
pass("volume_ratio", String(parsed.component_breakdown.volume_ratio));
|
|
340
|
+
pass("spread_pct", `${parsed.component_breakdown.spread_pct}%`);
|
|
341
|
+
pass("disclaimer", parsed.disclaimer.length > 0 ? "present" : "MISSING");
|
|
342
|
+
} catch (err) {
|
|
343
|
+
fail("get_market_sentiment", err);
|
|
344
|
+
failures++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ----------------------------------------------------------------
|
|
349
|
+
// 12. get_technical_indicators
|
|
350
|
+
// ----------------------------------------------------------------
|
|
351
|
+
section(`get_technical_indicators — ${TEST_MARKET} (1h, limit 1000)`);
|
|
352
|
+
{
|
|
353
|
+
try {
|
|
354
|
+
const result = await handleTechnicalIndicators(
|
|
355
|
+
{ market_id: TEST_MARKET, period: "1h", limit: 1000 },
|
|
356
|
+
client,
|
|
357
|
+
);
|
|
358
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
359
|
+
const parsed = JSON.parse(result.content[0].text) as {
|
|
360
|
+
candles_used?: number;
|
|
361
|
+
candles_available?: number;
|
|
362
|
+
warning?: string;
|
|
363
|
+
indicators: {
|
|
364
|
+
rsi: number | null;
|
|
365
|
+
macd: { line: number; signal: number; histogram: number } | null;
|
|
366
|
+
bollinger_bands: { upper: number; mid: number; lower: number } | null;
|
|
367
|
+
sma_20: number;
|
|
368
|
+
sma_50: number;
|
|
369
|
+
} | null;
|
|
370
|
+
signals: { rsi_signal: string; macd_signal: string; bb_signal: string };
|
|
371
|
+
disclaimer: string;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
if (parsed.warning === "insufficient_data") {
|
|
375
|
+
pass("warning", `insufficient_data (${parsed.candles_available} candles available, need 50)`);
|
|
376
|
+
} else {
|
|
377
|
+
pass("candles_used", String(parsed.candles_used));
|
|
378
|
+
if (!parsed.indicators) throw new Error("indicators is null without a warning");
|
|
379
|
+
pass("rsi", String(parsed.indicators.rsi));
|
|
380
|
+
pass("rsi_signal", parsed.signals.rsi_signal);
|
|
381
|
+
pass("macd_histogram", String(parsed.indicators.macd?.histogram));
|
|
382
|
+
pass("macd_signal", parsed.signals.macd_signal);
|
|
383
|
+
pass("bb_upper", String(parsed.indicators.bollinger_bands?.upper));
|
|
384
|
+
pass("bb_signal", parsed.signals.bb_signal);
|
|
385
|
+
pass("sma_20", String(parsed.indicators.sma_20));
|
|
386
|
+
pass("sma_50", String(parsed.indicators.sma_50));
|
|
387
|
+
pass("disclaimer", parsed.disclaimer.length > 0 ? "present" : "MISSING");
|
|
388
|
+
}
|
|
389
|
+
} catch (err) {
|
|
390
|
+
fail("get_technical_indicators", err);
|
|
391
|
+
failures++;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
228
395
|
// ----------------------------------------------------------------
|
|
229
396
|
// Auth tools: get_balances, get_orders, place_order, cancel_order
|
|
230
397
|
// ----------------------------------------------------------------
|
|
@@ -263,6 +430,34 @@ if (!client.hasAuth()) {
|
|
|
263
430
|
// cancel_order — confirmation guard test (must reject without CONFIRM)
|
|
264
431
|
console.log(" Skipping: cancel_order live execution (destructive — requires confirmation_token=CONFIRM)");
|
|
265
432
|
pass("cancel_order guard", "confirmation_token check enforced at tool layer (code-audited)");
|
|
433
|
+
|
|
434
|
+
// schedule_cancel_all — arm then immediately disarm (non-destructive)
|
|
435
|
+
try {
|
|
436
|
+
const armResult = await handleScheduleCancelAll(
|
|
437
|
+
{ market_id: TEST_MARKET, ttl_seconds: 300, confirmation_token: "CONFIRM" },
|
|
438
|
+
client,
|
|
439
|
+
);
|
|
440
|
+
if (armResult.isError) throw new Error(armResult.content[0].text);
|
|
441
|
+
const armed = JSON.parse(armResult.content[0].text) as {
|
|
442
|
+
active: boolean;
|
|
443
|
+
expires_at: string;
|
|
444
|
+
ttl_seconds: number;
|
|
445
|
+
warning: string;
|
|
446
|
+
};
|
|
447
|
+
if (!armed.active) throw new Error("active should be true after CONFIRM");
|
|
448
|
+
pass("schedule_cancel_all active", armed.active ? "true" : "false");
|
|
449
|
+
pass("schedule_cancel_all expires_at", armed.expires_at);
|
|
450
|
+
pass("schedule_cancel_all warning", armed.warning.length > 0 ? "present" : "MISSING");
|
|
451
|
+
|
|
452
|
+
// Immediately disarm so no orders are cancelled
|
|
453
|
+
const disarmResult = handleDisarmCancelTimer({ market_id: TEST_MARKET });
|
|
454
|
+
if (disarmResult.isError) throw new Error(disarmResult.content[0].text);
|
|
455
|
+
const disarmed = JSON.parse(disarmResult.content[0].text) as { disarmed: boolean };
|
|
456
|
+
pass("disarm_cancel_timer", disarmed.disarmed ? "timer cleared ✓" : "FAILED to disarm");
|
|
457
|
+
} catch (err) {
|
|
458
|
+
fail("schedule_cancel_all / disarm_cancel_timer", err);
|
|
459
|
+
failures++;
|
|
460
|
+
}
|
|
266
461
|
}
|
|
267
462
|
|
|
268
463
|
// ----------------------------------------------------------------
|
|
@@ -271,6 +466,8 @@ if (!client.hasAuth()) {
|
|
|
271
466
|
section("Summary");
|
|
272
467
|
if (failures === 0) {
|
|
273
468
|
console.log(" All tools returned valid data from the live Buda API.");
|
|
469
|
+
console.log(" Coverage: simulate_order, calculate_position_size, get_market_sentiment,");
|
|
470
|
+
console.log(" get_technical_indicators, schedule_cancel_all/disarm (auth-gated if credentials set).");
|
|
274
471
|
} else {
|
|
275
472
|
console.error(` ${failures} tool(s) failed. See errors above.`);
|
|
276
473
|
process.exit(1);
|