@guiie/buda-mcp 1.4.1 → 1.4.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 +10 -0
- package/dist/tools/price_history.js +4 -4
- package/dist/tools/technical_indicators.d.ts +1 -1
- package/dist/tools/technical_indicators.d.ts.map +1 -1
- package/dist/tools/technical_indicators.js +6 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +3 -0
- package/marketplace/openapi.yaml +375 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/price_history.ts +4 -4
- package/src/tools/technical_indicators.ts +7 -6
- package/src/utils.ts +6 -3
- package/test/run-all.ts +55 -27
- package/test/unit.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ This project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.4.2] – 2026-04-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Shorter candle periods** (`5m`, `15m`, `30m`) now supported in both `get_price_history` and `get_technical_indicators`. Previously only `1h`, `4h`, `1d` were available.
|
|
15
|
+
- **Lowered `MIN_CANDLES`** in `get_technical_indicators` from 50 to 20, matching the actual minimum required by the algorithms (RSI-14, MACD-26, BB-20). Individual indicators that still lack enough data return `null`.
|
|
16
|
+
- **Integration tests** now cover the full `get_technical_indicators` indicators branch using `5m` period (42 live candles from BTC-CLP). Previously only the `insufficient_data` branch was tested live.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
10
20
|
## [1.4.1] – 2026-04-11
|
|
11
21
|
|
|
12
22
|
### Fixed
|
|
@@ -7,7 +7,7 @@ export const toolSchema = {
|
|
|
7
7
|
description: "IMPORTANT: Candles are aggregated client-side from raw trades (Buda has no native candlestick " +
|
|
8
8
|
"endpoint) — fetching more trades via the 'limit' parameter gives deeper history but slower " +
|
|
9
9
|
"responses. Returns OHLCV candles (open/high/low/close as floats in quote currency; volume as float " +
|
|
10
|
-
"in base currency) for periods 1h, 4h, or 1d. Candle timestamps are UTC bucket boundaries. " +
|
|
10
|
+
"in base currency) for periods 5m, 15m, 30m, 1h, 4h, or 1d. Candle timestamps are UTC bucket boundaries. " +
|
|
11
11
|
"Example: 'Show me the hourly BTC-CLP price chart for the past 24 hours.'",
|
|
12
12
|
inputSchema: {
|
|
13
13
|
type: "object",
|
|
@@ -18,7 +18,7 @@ export const toolSchema = {
|
|
|
18
18
|
},
|
|
19
19
|
period: {
|
|
20
20
|
type: "string",
|
|
21
|
-
description: "Candle period: '
|
|
21
|
+
description: "Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'.",
|
|
22
22
|
},
|
|
23
23
|
limit: {
|
|
24
24
|
type: "number",
|
|
@@ -35,9 +35,9 @@ export function register(server, client, _cache) {
|
|
|
35
35
|
.string()
|
|
36
36
|
.describe("Market ID (e.g. 'BTC-CLP', 'ETH-BTC')."),
|
|
37
37
|
period: z
|
|
38
|
-
.enum(["1h", "4h", "1d"])
|
|
38
|
+
.enum(["5m", "15m", "30m", "1h", "4h", "1d"])
|
|
39
39
|
.default("1h")
|
|
40
|
-
.describe("Candle period: '
|
|
40
|
+
.describe("Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'."),
|
|
41
41
|
limit: z
|
|
42
42
|
.number()
|
|
43
43
|
.int()
|
|
@@ -24,7 +24,7 @@ export declare const toolSchema: {
|
|
|
24
24
|
};
|
|
25
25
|
type TechnicalIndicatorsArgs = {
|
|
26
26
|
market_id: string;
|
|
27
|
-
period: "1h" | "4h" | "1d";
|
|
27
|
+
period: "5m" | "15m" | "30m" | "1h" | "4h" | "1d";
|
|
28
28
|
limit?: number;
|
|
29
29
|
};
|
|
30
30
|
export declare function handleTechnicalIndicators(args: TechnicalIndicatorsArgs, client: BudaClient): Promise<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"technical_indicators.d.ts","sourceRoot":"","sources":["../../src/tools/technical_indicators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAKxD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"technical_indicators.d.ts","sourceRoot":"","sources":["../../src/tools/technical_indicators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AAKxD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;CA8BtB,CAAC;AAsGF,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,IAAI,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,uBAAuB,EAC7B,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC;IAAE,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CA2GhF;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAyBpE"}
|
|
@@ -6,9 +6,10 @@ export const toolSchema = {
|
|
|
6
6
|
name: "get_technical_indicators",
|
|
7
7
|
description: "Computes RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50 " +
|
|
8
8
|
"from Buda trade history — no external data or libraries. " +
|
|
9
|
+
"Supports periods: 5m, 15m, 30m, 1h, 4h, 1d. Use shorter periods (5m/15m) for intraday analysis. " +
|
|
9
10
|
"Uses at least 500 trades for reliable results (set limit=1000 for maximum depth). " +
|
|
10
11
|
"Returns latest indicator values and signal interpretations (overbought/oversold, crossover, band position). " +
|
|
11
|
-
"If fewer than
|
|
12
|
+
"If fewer than 20 candles are available after aggregation, returns a structured warning instead. " +
|
|
12
13
|
"Example: 'Is BTC-CLP RSI overbought on the 4-hour chart?'",
|
|
13
14
|
inputSchema: {
|
|
14
15
|
type: "object",
|
|
@@ -19,7 +20,7 @@ export const toolSchema = {
|
|
|
19
20
|
},
|
|
20
21
|
period: {
|
|
21
22
|
type: "string",
|
|
22
|
-
description: "Candle period: '1h', '4h', or '1d'. Default: '1h'.",
|
|
23
|
+
description: "Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'.",
|
|
23
24
|
},
|
|
24
25
|
limit: {
|
|
25
26
|
type: "number",
|
|
@@ -108,7 +109,7 @@ function bollingerBands(closes, period = 20, stdMult = 2) {
|
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
111
|
// ---- Tool handler ----
|
|
111
|
-
const MIN_CANDLES =
|
|
112
|
+
const MIN_CANDLES = 20;
|
|
112
113
|
export async function handleTechnicalIndicators(args, client) {
|
|
113
114
|
const { market_id, period, limit } = args;
|
|
114
115
|
const validationError = validateMarketId(market_id);
|
|
@@ -207,9 +208,9 @@ export function register(server, client) {
|
|
|
207
208
|
.string()
|
|
208
209
|
.describe("Market ID (e.g. 'BTC-CLP', 'ETH-BTC')."),
|
|
209
210
|
period: z
|
|
210
|
-
.enum(["1h", "4h", "1d"])
|
|
211
|
+
.enum(["5m", "15m", "30m", "1h", "4h", "1d"])
|
|
211
212
|
.default("1h")
|
|
212
|
-
.describe("Candle period: '1h', '4h', or '1d'. Default: '1h'."),
|
|
213
|
+
.describe("Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'."),
|
|
213
214
|
limit: z
|
|
214
215
|
.number()
|
|
215
216
|
.int()
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAI/E;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAI/E;AAWD;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAC3C,MAAM,EAAE,MAAM,GACb,WAAW,EAAE,CAoCf"}
|
package/dist/utils.js
CHANGED
package/marketplace/openapi.yaml
CHANGED
|
@@ -14,7 +14,7 @@ info:
|
|
|
14
14
|
stdio server. Deploy locally with mcp-proxy:
|
|
15
15
|
mcp-proxy --port 8000 -- npx -y @guiie/buda-mcp
|
|
16
16
|
Or point `servers[0].url` at your hosted instance.
|
|
17
|
-
version: 1.4.
|
|
17
|
+
version: 1.4.1
|
|
18
18
|
contact:
|
|
19
19
|
url: https://github.com/gtorreal/buda-mcp
|
|
20
20
|
|
|
@@ -350,6 +350,189 @@ paths:
|
|
|
350
350
|
"404":
|
|
351
351
|
$ref: "#/components/responses/NotFound"
|
|
352
352
|
|
|
353
|
+
/simulate_order:
|
|
354
|
+
get:
|
|
355
|
+
operationId: simulateOrder
|
|
356
|
+
summary: Simulate a buy or sell order without placing it
|
|
357
|
+
description: |
|
|
358
|
+
Simulates a buy or sell order using live ticker data — no order is ever placed.
|
|
359
|
+
Returns estimated fill price, fee amount, total cost, and slippage vs mid-price.
|
|
360
|
+
Omit 'price' for a market order simulation; supply 'price' for a limit order simulation.
|
|
361
|
+
All outputs include simulation: true.
|
|
362
|
+
Example: 'How much would it cost to buy 0.01 BTC on BTC-CLP right now?'
|
|
363
|
+
parameters:
|
|
364
|
+
- name: market_id
|
|
365
|
+
in: query
|
|
366
|
+
required: true
|
|
367
|
+
description: Market identifier (e.g. "BTC-CLP", "ETH-BTC"). Case-insensitive.
|
|
368
|
+
schema:
|
|
369
|
+
type: string
|
|
370
|
+
example: BTC-CLP
|
|
371
|
+
- name: side
|
|
372
|
+
in: query
|
|
373
|
+
required: true
|
|
374
|
+
description: Order side — "buy" or "sell".
|
|
375
|
+
schema:
|
|
376
|
+
type: string
|
|
377
|
+
enum: [buy, sell]
|
|
378
|
+
- name: amount
|
|
379
|
+
in: query
|
|
380
|
+
required: true
|
|
381
|
+
description: Order size in base currency (e.g. BTC for BTC-CLP).
|
|
382
|
+
schema:
|
|
383
|
+
type: number
|
|
384
|
+
minimum: 0
|
|
385
|
+
example: 0.01
|
|
386
|
+
- name: price
|
|
387
|
+
in: query
|
|
388
|
+
required: false
|
|
389
|
+
description: Limit price in quote currency. Omit for a market order simulation.
|
|
390
|
+
schema:
|
|
391
|
+
type: number
|
|
392
|
+
minimum: 0
|
|
393
|
+
example: 80000000
|
|
394
|
+
responses:
|
|
395
|
+
"200":
|
|
396
|
+
description: Order simulation result
|
|
397
|
+
content:
|
|
398
|
+
application/json:
|
|
399
|
+
schema:
|
|
400
|
+
$ref: "#/components/schemas/SimulateOrderResponse"
|
|
401
|
+
"404":
|
|
402
|
+
$ref: "#/components/responses/NotFound"
|
|
403
|
+
|
|
404
|
+
/calculate_position_size:
|
|
405
|
+
get:
|
|
406
|
+
operationId: calculatePositionSize
|
|
407
|
+
summary: Calculate position size from capital and risk parameters
|
|
408
|
+
description: |
|
|
409
|
+
Calculates position size based on capital, risk tolerance, entry price, and stop-loss.
|
|
410
|
+
Determines how many units to buy or sell so that a stop-loss hit costs exactly risk_pct% of capital.
|
|
411
|
+
Fully client-side — no API call is made.
|
|
412
|
+
Example: 'How many BTC can I buy with 1,000,000 CLP, risking 2%, entry 80,000,000 CLP, stop at 78,000,000 CLP?'
|
|
413
|
+
parameters:
|
|
414
|
+
- name: market_id
|
|
415
|
+
in: query
|
|
416
|
+
required: true
|
|
417
|
+
description: Market identifier (e.g. "BTC-CLP"). Used to derive base and quote currencies.
|
|
418
|
+
schema:
|
|
419
|
+
type: string
|
|
420
|
+
example: BTC-CLP
|
|
421
|
+
- name: capital
|
|
422
|
+
in: query
|
|
423
|
+
required: true
|
|
424
|
+
description: Total available capital in the quote currency (e.g. CLP for BTC-CLP).
|
|
425
|
+
schema:
|
|
426
|
+
type: number
|
|
427
|
+
minimum: 0
|
|
428
|
+
example: 1000000
|
|
429
|
+
- name: risk_pct
|
|
430
|
+
in: query
|
|
431
|
+
required: true
|
|
432
|
+
description: Percentage of capital to risk on this trade (0.1–10, e.g. 2 means 2%).
|
|
433
|
+
schema:
|
|
434
|
+
type: number
|
|
435
|
+
minimum: 0.1
|
|
436
|
+
maximum: 10
|
|
437
|
+
example: 2
|
|
438
|
+
- name: entry_price
|
|
439
|
+
in: query
|
|
440
|
+
required: true
|
|
441
|
+
description: Planned entry price in quote currency.
|
|
442
|
+
schema:
|
|
443
|
+
type: number
|
|
444
|
+
minimum: 0
|
|
445
|
+
example: 80000000
|
|
446
|
+
- name: stop_loss_price
|
|
447
|
+
in: query
|
|
448
|
+
required: true
|
|
449
|
+
description: Stop-loss price in quote currency. Must differ from entry_price.
|
|
450
|
+
schema:
|
|
451
|
+
type: number
|
|
452
|
+
minimum: 0
|
|
453
|
+
example: 78000000
|
|
454
|
+
responses:
|
|
455
|
+
"200":
|
|
456
|
+
description: Position sizing result
|
|
457
|
+
content:
|
|
458
|
+
application/json:
|
|
459
|
+
schema:
|
|
460
|
+
$ref: "#/components/schemas/PositionSizeResponse"
|
|
461
|
+
"404":
|
|
462
|
+
$ref: "#/components/responses/NotFound"
|
|
463
|
+
|
|
464
|
+
/get_market_sentiment:
|
|
465
|
+
get:
|
|
466
|
+
operationId: getMarketSentiment
|
|
467
|
+
summary: Composite sentiment score for a market
|
|
468
|
+
description: |
|
|
469
|
+
Computes a composite sentiment score (−100 to +100) based on 24h price variation (40%),
|
|
470
|
+
volume vs 7-day average (35%), and bid/ask spread vs baseline (25%).
|
|
471
|
+
Returns a score, a label (bearish/neutral/bullish), and a full component breakdown.
|
|
472
|
+
Example: 'Is the BTC-CLP market currently bullish or bearish?'
|
|
473
|
+
parameters:
|
|
474
|
+
- name: market_id
|
|
475
|
+
in: query
|
|
476
|
+
required: true
|
|
477
|
+
description: Market identifier (e.g. "BTC-CLP", "ETH-BTC"). Case-insensitive.
|
|
478
|
+
schema:
|
|
479
|
+
type: string
|
|
480
|
+
example: BTC-CLP
|
|
481
|
+
responses:
|
|
482
|
+
"200":
|
|
483
|
+
description: Market sentiment result
|
|
484
|
+
content:
|
|
485
|
+
application/json:
|
|
486
|
+
schema:
|
|
487
|
+
$ref: "#/components/schemas/MarketSentimentResponse"
|
|
488
|
+
"404":
|
|
489
|
+
$ref: "#/components/responses/NotFound"
|
|
490
|
+
|
|
491
|
+
/get_technical_indicators:
|
|
492
|
+
get:
|
|
493
|
+
operationId: getTechnicalIndicators
|
|
494
|
+
summary: RSI, MACD, Bollinger Bands, SMA20, SMA50 from trade history
|
|
495
|
+
description: |
|
|
496
|
+
Computes RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50
|
|
497
|
+
from Buda trade history — no external data or libraries required.
|
|
498
|
+
Uses at least 500 trades for reliable results (set limit=1000 for maximum depth).
|
|
499
|
+
Returns latest indicator values and signal interpretations (overbought/oversold, crossover, band position).
|
|
500
|
+
If fewer than 50 candles are available after aggregation, returns a structured warning instead.
|
|
501
|
+
Example: 'Is BTC-CLP RSI overbought on the 4-hour chart?'
|
|
502
|
+
parameters:
|
|
503
|
+
- name: market_id
|
|
504
|
+
in: query
|
|
505
|
+
required: true
|
|
506
|
+
description: Market identifier (e.g. "BTC-CLP", "ETH-BTC"). Case-insensitive.
|
|
507
|
+
schema:
|
|
508
|
+
type: string
|
|
509
|
+
example: BTC-CLP
|
|
510
|
+
- name: period
|
|
511
|
+
in: query
|
|
512
|
+
required: false
|
|
513
|
+
description: Candle period. One of "1h", "4h", "1d". Default is "1h".
|
|
514
|
+
schema:
|
|
515
|
+
type: string
|
|
516
|
+
enum: ["1h", "4h", "1d"]
|
|
517
|
+
default: "1h"
|
|
518
|
+
- name: limit
|
|
519
|
+
in: query
|
|
520
|
+
required: false
|
|
521
|
+
description: Number of raw trades to fetch (default 500, max 1000). More trades = more candles = more reliable indicators.
|
|
522
|
+
schema:
|
|
523
|
+
type: integer
|
|
524
|
+
minimum: 500
|
|
525
|
+
maximum: 1000
|
|
526
|
+
responses:
|
|
527
|
+
"200":
|
|
528
|
+
description: Technical indicators result
|
|
529
|
+
content:
|
|
530
|
+
application/json:
|
|
531
|
+
schema:
|
|
532
|
+
$ref: "#/components/schemas/TechnicalIndicatorsResponse"
|
|
533
|
+
"404":
|
|
534
|
+
$ref: "#/components/responses/NotFound"
|
|
535
|
+
|
|
353
536
|
components:
|
|
354
537
|
schemas:
|
|
355
538
|
Market:
|
|
@@ -747,6 +930,197 @@ components:
|
|
|
747
930
|
type: string
|
|
748
931
|
example: "Buda taker fee is 0.8% per leg. A round-trip arbitrage costs approximately 1.6% in fees."
|
|
749
932
|
|
|
933
|
+
SimulateOrderResponse:
|
|
934
|
+
type: object
|
|
935
|
+
properties:
|
|
936
|
+
simulation:
|
|
937
|
+
type: boolean
|
|
938
|
+
example: true
|
|
939
|
+
market_id:
|
|
940
|
+
type: string
|
|
941
|
+
example: BTC-CLP
|
|
942
|
+
side:
|
|
943
|
+
type: string
|
|
944
|
+
enum: [buy, sell]
|
|
945
|
+
amount:
|
|
946
|
+
type: number
|
|
947
|
+
example: 0.01
|
|
948
|
+
order_type_assumed:
|
|
949
|
+
type: string
|
|
950
|
+
enum: [market, limit]
|
|
951
|
+
estimated_fill_price:
|
|
952
|
+
type: number
|
|
953
|
+
example: 80000000
|
|
954
|
+
price_currency:
|
|
955
|
+
type: string
|
|
956
|
+
example: CLP
|
|
957
|
+
fee_amount:
|
|
958
|
+
type: number
|
|
959
|
+
example: 6400
|
|
960
|
+
fee_currency:
|
|
961
|
+
type: string
|
|
962
|
+
example: CLP
|
|
963
|
+
fee_rate_pct:
|
|
964
|
+
type: number
|
|
965
|
+
example: 0.8
|
|
966
|
+
total_cost:
|
|
967
|
+
type: number
|
|
968
|
+
description: Total outlay for buys (gross + fee) or net proceeds for sells (gross − fee).
|
|
969
|
+
example: 806400
|
|
970
|
+
slippage_vs_mid_pct:
|
|
971
|
+
type: number
|
|
972
|
+
description: Percentage difference between estimated fill price and mid-price.
|
|
973
|
+
example: 0.0027
|
|
974
|
+
mid_price:
|
|
975
|
+
type: number
|
|
976
|
+
example: 79997840
|
|
977
|
+
|
|
978
|
+
PositionSizeResponse:
|
|
979
|
+
type: object
|
|
980
|
+
properties:
|
|
981
|
+
market_id:
|
|
982
|
+
type: string
|
|
983
|
+
example: BTC-CLP
|
|
984
|
+
side:
|
|
985
|
+
type: string
|
|
986
|
+
enum: [buy, sell]
|
|
987
|
+
units:
|
|
988
|
+
type: number
|
|
989
|
+
description: Number of base currency units to trade.
|
|
990
|
+
example: 0.01
|
|
991
|
+
base_currency:
|
|
992
|
+
type: string
|
|
993
|
+
example: BTC
|
|
994
|
+
capital_at_risk:
|
|
995
|
+
type: number
|
|
996
|
+
description: Amount of capital at risk (capital × risk_pct / 100) in quote currency.
|
|
997
|
+
example: 20000
|
|
998
|
+
position_value:
|
|
999
|
+
type: number
|
|
1000
|
+
description: Total position value in quote currency (units × entry_price).
|
|
1001
|
+
example: 800000
|
|
1002
|
+
fee_impact:
|
|
1003
|
+
type: number
|
|
1004
|
+
description: Estimated entry fee at 0.8% taker rate in quote currency.
|
|
1005
|
+
example: 6400
|
|
1006
|
+
fee_currency:
|
|
1007
|
+
type: string
|
|
1008
|
+
example: CLP
|
|
1009
|
+
risk_reward_note:
|
|
1010
|
+
type: string
|
|
1011
|
+
description: Human-readable summary of the trade setup.
|
|
1012
|
+
|
|
1013
|
+
MarketSentimentResponse:
|
|
1014
|
+
type: object
|
|
1015
|
+
properties:
|
|
1016
|
+
market_id:
|
|
1017
|
+
type: string
|
|
1018
|
+
example: BTC-CLP
|
|
1019
|
+
score:
|
|
1020
|
+
type: number
|
|
1021
|
+
description: Composite sentiment score from −100 (very bearish) to +100 (very bullish).
|
|
1022
|
+
example: 23.5
|
|
1023
|
+
label:
|
|
1024
|
+
type: string
|
|
1025
|
+
enum: [bearish, neutral, bullish]
|
|
1026
|
+
example: bullish
|
|
1027
|
+
component_breakdown:
|
|
1028
|
+
type: object
|
|
1029
|
+
properties:
|
|
1030
|
+
price_variation_24h_pct:
|
|
1031
|
+
type: number
|
|
1032
|
+
example: 1.9
|
|
1033
|
+
volume_ratio:
|
|
1034
|
+
type: number
|
|
1035
|
+
description: Today's volume vs 7-day daily average (1.0 = average).
|
|
1036
|
+
example: 1.25
|
|
1037
|
+
spread_pct:
|
|
1038
|
+
type: number
|
|
1039
|
+
example: 0.005
|
|
1040
|
+
spread_baseline_pct:
|
|
1041
|
+
type: number
|
|
1042
|
+
example: 1.0
|
|
1043
|
+
price_score:
|
|
1044
|
+
type: number
|
|
1045
|
+
volume_score:
|
|
1046
|
+
type: number
|
|
1047
|
+
spread_score:
|
|
1048
|
+
type: number
|
|
1049
|
+
data_timestamp:
|
|
1050
|
+
type: string
|
|
1051
|
+
format: date-time
|
|
1052
|
+
disclaimer:
|
|
1053
|
+
type: string
|
|
1054
|
+
|
|
1055
|
+
TechnicalIndicatorsResponse:
|
|
1056
|
+
type: object
|
|
1057
|
+
properties:
|
|
1058
|
+
market_id:
|
|
1059
|
+
type: string
|
|
1060
|
+
example: BTC-CLP
|
|
1061
|
+
period:
|
|
1062
|
+
type: string
|
|
1063
|
+
enum: ["1h", "4h", "1d"]
|
|
1064
|
+
candles_used:
|
|
1065
|
+
type: integer
|
|
1066
|
+
example: 85
|
|
1067
|
+
indicators:
|
|
1068
|
+
type: object
|
|
1069
|
+
nullable: true
|
|
1070
|
+
properties:
|
|
1071
|
+
rsi:
|
|
1072
|
+
type: number
|
|
1073
|
+
nullable: true
|
|
1074
|
+
description: RSI(14). null if insufficient data.
|
|
1075
|
+
example: 62.4
|
|
1076
|
+
macd:
|
|
1077
|
+
type: object
|
|
1078
|
+
nullable: true
|
|
1079
|
+
properties:
|
|
1080
|
+
line:
|
|
1081
|
+
type: number
|
|
1082
|
+
signal:
|
|
1083
|
+
type: number
|
|
1084
|
+
histogram:
|
|
1085
|
+
type: number
|
|
1086
|
+
bollinger_bands:
|
|
1087
|
+
type: object
|
|
1088
|
+
nullable: true
|
|
1089
|
+
properties:
|
|
1090
|
+
upper:
|
|
1091
|
+
type: number
|
|
1092
|
+
mid:
|
|
1093
|
+
type: number
|
|
1094
|
+
lower:
|
|
1095
|
+
type: number
|
|
1096
|
+
sma_20:
|
|
1097
|
+
type: number
|
|
1098
|
+
sma_50:
|
|
1099
|
+
type: number
|
|
1100
|
+
signals:
|
|
1101
|
+
type: object
|
|
1102
|
+
properties:
|
|
1103
|
+
rsi_signal:
|
|
1104
|
+
type: string
|
|
1105
|
+
enum: [overbought, oversold, neutral]
|
|
1106
|
+
macd_signal:
|
|
1107
|
+
type: string
|
|
1108
|
+
enum: [bullish_crossover, bearish_crossover, neutral]
|
|
1109
|
+
bb_signal:
|
|
1110
|
+
type: string
|
|
1111
|
+
enum: [above_upper, below_lower, within_bands]
|
|
1112
|
+
warning:
|
|
1113
|
+
type: string
|
|
1114
|
+
description: Present only when candles_available < 50. Value is "insufficient_data".
|
|
1115
|
+
candles_available:
|
|
1116
|
+
type: integer
|
|
1117
|
+
description: Present only when warning is set.
|
|
1118
|
+
minimum_required:
|
|
1119
|
+
type: integer
|
|
1120
|
+
description: Present only when warning is set. Always 50.
|
|
1121
|
+
disclaimer:
|
|
1122
|
+
type: string
|
|
1123
|
+
|
|
750
1124
|
Error:
|
|
751
1125
|
type: object
|
|
752
1126
|
properties:
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/gtorreal/buda-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.4.
|
|
9
|
+
"version": "1.4.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@guiie/buda-mcp",
|
|
14
|
-
"version": "1.4.
|
|
14
|
+
"version": "1.4.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|
|
@@ -12,7 +12,7 @@ export const toolSchema = {
|
|
|
12
12
|
"IMPORTANT: Candles are aggregated client-side from raw trades (Buda has no native candlestick " +
|
|
13
13
|
"endpoint) — fetching more trades via the 'limit' parameter gives deeper history but slower " +
|
|
14
14
|
"responses. Returns OHLCV candles (open/high/low/close as floats in quote currency; volume as float " +
|
|
15
|
-
"in base currency) for periods 1h, 4h, or 1d. Candle timestamps are UTC bucket boundaries. " +
|
|
15
|
+
"in base currency) for periods 5m, 15m, 30m, 1h, 4h, or 1d. Candle timestamps are UTC bucket boundaries. " +
|
|
16
16
|
"Example: 'Show me the hourly BTC-CLP price chart for the past 24 hours.'",
|
|
17
17
|
inputSchema: {
|
|
18
18
|
type: "object" as const,
|
|
@@ -23,7 +23,7 @@ export const toolSchema = {
|
|
|
23
23
|
},
|
|
24
24
|
period: {
|
|
25
25
|
type: "string",
|
|
26
|
-
description: "Candle period: '
|
|
26
|
+
description: "Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'.",
|
|
27
27
|
},
|
|
28
28
|
limit: {
|
|
29
29
|
type: "number",
|
|
@@ -45,9 +45,9 @@ export function register(server: McpServer, client: BudaClient, _cache: MemoryCa
|
|
|
45
45
|
.string()
|
|
46
46
|
.describe("Market ID (e.g. 'BTC-CLP', 'ETH-BTC')."),
|
|
47
47
|
period: z
|
|
48
|
-
.enum(["1h", "4h", "1d"])
|
|
48
|
+
.enum(["5m", "15m", "30m", "1h", "4h", "1d"])
|
|
49
49
|
.default("1h")
|
|
50
|
-
.describe("Candle period: '
|
|
50
|
+
.describe("Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'."),
|
|
51
51
|
limit: z
|
|
52
52
|
.number()
|
|
53
53
|
.int()
|
|
@@ -10,9 +10,10 @@ export const toolSchema = {
|
|
|
10
10
|
description:
|
|
11
11
|
"Computes RSI (14), MACD (12/26/9), Bollinger Bands (20, 2σ), SMA 20, and SMA 50 " +
|
|
12
12
|
"from Buda trade history — no external data or libraries. " +
|
|
13
|
+
"Supports periods: 5m, 15m, 30m, 1h, 4h, 1d. Use shorter periods (5m/15m) for intraday analysis. " +
|
|
13
14
|
"Uses at least 500 trades for reliable results (set limit=1000 for maximum depth). " +
|
|
14
15
|
"Returns latest indicator values and signal interpretations (overbought/oversold, crossover, band position). " +
|
|
15
|
-
"If fewer than
|
|
16
|
+
"If fewer than 20 candles are available after aggregation, returns a structured warning instead. " +
|
|
16
17
|
"Example: 'Is BTC-CLP RSI overbought on the 4-hour chart?'",
|
|
17
18
|
inputSchema: {
|
|
18
19
|
type: "object" as const,
|
|
@@ -23,7 +24,7 @@ export const toolSchema = {
|
|
|
23
24
|
},
|
|
24
25
|
period: {
|
|
25
26
|
type: "string",
|
|
26
|
-
description: "Candle period: '1h', '4h', or '1d'. Default: '1h'.",
|
|
27
|
+
description: "Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'.",
|
|
27
28
|
},
|
|
28
29
|
limit: {
|
|
29
30
|
type: "number",
|
|
@@ -134,11 +135,11 @@ function bollingerBands(closes: number[], period: number = 20, stdMult: number =
|
|
|
134
135
|
|
|
135
136
|
// ---- Tool handler ----
|
|
136
137
|
|
|
137
|
-
const MIN_CANDLES =
|
|
138
|
+
const MIN_CANDLES = 20;
|
|
138
139
|
|
|
139
140
|
type TechnicalIndicatorsArgs = {
|
|
140
141
|
market_id: string;
|
|
141
|
-
period: "1h" | "4h" | "1d";
|
|
142
|
+
period: "5m" | "15m" | "30m" | "1h" | "4h" | "1d";
|
|
142
143
|
limit?: number;
|
|
143
144
|
};
|
|
144
145
|
|
|
@@ -263,9 +264,9 @@ export function register(server: McpServer, client: BudaClient): void {
|
|
|
263
264
|
.string()
|
|
264
265
|
.describe("Market ID (e.g. 'BTC-CLP', 'ETH-BTC')."),
|
|
265
266
|
period: z
|
|
266
|
-
.enum(["1h", "4h", "1d"])
|
|
267
|
+
.enum(["5m", "15m", "30m", "1h", "4h", "1d"])
|
|
267
268
|
.default("1h")
|
|
268
|
-
.describe("Candle period: '1h', '4h', or '1d'. Default: '1h'."),
|
|
269
|
+
.describe("Candle period: '5m', '15m', '30m', '1h', '4h', or '1d'. Default: '1h'."),
|
|
269
270
|
limit: z
|
|
270
271
|
.number()
|
|
271
272
|
.int()
|
package/src/utils.ts
CHANGED
|
@@ -21,9 +21,12 @@ export function getLiquidityRating(spreadPct: number): "high" | "medium" | "low"
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const PERIOD_MS: Record<string, number> = {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
24
|
+
"5m": 5 * 60 * 1000,
|
|
25
|
+
"15m": 15 * 60 * 1000,
|
|
26
|
+
"30m": 30 * 60 * 1000,
|
|
27
|
+
"1h": 60 * 60 * 1000,
|
|
28
|
+
"4h": 4 * 60 * 60 * 1000,
|
|
29
|
+
"1d": 24 * 60 * 60 * 1000,
|
|
27
30
|
};
|
|
28
31
|
|
|
29
32
|
/**
|
package/test/run-all.ts
CHANGED
|
@@ -348,7 +348,24 @@ section(`get_market_sentiment — ${TEST_MARKET}`);
|
|
|
348
348
|
// ----------------------------------------------------------------
|
|
349
349
|
// 12. get_technical_indicators
|
|
350
350
|
// ----------------------------------------------------------------
|
|
351
|
-
|
|
351
|
+
|
|
352
|
+
type TechIndicatorsResponse = {
|
|
353
|
+
candles_used?: number;
|
|
354
|
+
candles_available?: number;
|
|
355
|
+
warning?: string;
|
|
356
|
+
indicators: {
|
|
357
|
+
rsi: number | null;
|
|
358
|
+
macd: { line: number; signal: number; histogram: number } | null;
|
|
359
|
+
bollinger_bands: { upper: number; mid: number; lower: number } | null;
|
|
360
|
+
sma_20: number;
|
|
361
|
+
sma_50: number;
|
|
362
|
+
} | null;
|
|
363
|
+
signals: { rsi_signal: string; macd_signal: string; bb_signal: string };
|
|
364
|
+
disclaimer: string;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// 12a. 1h period — expected to hit insufficient_data (BTC-CLP has ~8 candles/1h)
|
|
368
|
+
section(`get_technical_indicators — ${TEST_MARKET} (1h, insufficient_data branch)`);
|
|
352
369
|
{
|
|
353
370
|
try {
|
|
354
371
|
const result = await handleTechnicalIndicators(
|
|
@@ -356,38 +373,49 @@ section(`get_technical_indicators — ${TEST_MARKET} (1h, limit 1000)`);
|
|
|
356
373
|
client,
|
|
357
374
|
);
|
|
358
375
|
if (result.isError) throw new Error(result.content[0].text);
|
|
359
|
-
const parsed = JSON.parse(result.content[0].text) as
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
376
|
+
const parsed = JSON.parse(result.content[0].text) as TechIndicatorsResponse;
|
|
377
|
+
if (parsed.warning !== "insufficient_data") {
|
|
378
|
+
pass("note", `got ${parsed.candles_used} candles — unexpectedly enough data, indicators returned`);
|
|
379
|
+
} else {
|
|
380
|
+
pass("warning", `insufficient_data — ${parsed.candles_available} candles available (need 50) ✓`);
|
|
381
|
+
pass("indicators", parsed.indicators === null ? "null ✓" : "SHOULD BE NULL");
|
|
382
|
+
}
|
|
383
|
+
} catch (err) {
|
|
384
|
+
fail("get_technical_indicators (1h)", err);
|
|
385
|
+
failures++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// 12b. 5m period — enough candles to compute real indicators (~42 from last 100 trades)
|
|
390
|
+
section(`get_technical_indicators — ${TEST_MARKET} (5m, indicators branch)`);
|
|
391
|
+
{
|
|
392
|
+
try {
|
|
393
|
+
const result = await handleTechnicalIndicators(
|
|
394
|
+
{ market_id: TEST_MARKET, period: "5m", limit: 1000 },
|
|
395
|
+
client,
|
|
396
|
+
);
|
|
397
|
+
if (result.isError) throw new Error(result.content[0].text);
|
|
398
|
+
const parsed = JSON.parse(result.content[0].text) as TechIndicatorsResponse;
|
|
373
399
|
|
|
374
400
|
if (parsed.warning === "insufficient_data") {
|
|
375
|
-
|
|
401
|
+
// Market too quiet right now — report but don't fail
|
|
402
|
+
pass("note", `insufficient_data with 1m period (${parsed.candles_available} candles) — market unusually quiet`);
|
|
376
403
|
} else {
|
|
377
|
-
pass("candles_used", String(parsed.candles_used));
|
|
378
404
|
if (!parsed.indicators) throw new Error("indicators is null without a warning");
|
|
379
|
-
pass("
|
|
380
|
-
pass("
|
|
381
|
-
pass("macd_histogram",
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
pass("
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
pass("
|
|
405
|
+
pass("candles_used", String(parsed.candles_used));
|
|
406
|
+
pass("rsi", parsed.indicators.rsi !== null ? `${parsed.indicators.rsi} (${parsed.signals.rsi_signal})` : "null (insufficient RSI data)");
|
|
407
|
+
pass("macd_histogram", parsed.indicators.macd !== null
|
|
408
|
+
? `${parsed.indicators.macd.histogram.toFixed(2)} (${parsed.signals.macd_signal})`
|
|
409
|
+
: "null (insufficient MACD data)");
|
|
410
|
+
pass("bb_upper", parsed.indicators.bollinger_bands !== null
|
|
411
|
+
? `${parsed.indicators.bollinger_bands.upper.toLocaleString()} (${parsed.signals.bb_signal})`
|
|
412
|
+
: "null (insufficient BB data)");
|
|
413
|
+
pass("sma_20", String(parsed.indicators.sma_20?.toLocaleString()));
|
|
414
|
+
pass("sma_50", parsed.indicators.sma_50 !== null ? String(parsed.indicators.sma_50?.toLocaleString()) : "null (need 50 candles)");
|
|
415
|
+
pass("disclaimer", parsed.disclaimer?.length > 0 ? "present ✓" : "MISSING");
|
|
388
416
|
}
|
|
389
417
|
} catch (err) {
|
|
390
|
-
fail("get_technical_indicators", err);
|
|
418
|
+
fail("get_technical_indicators (1m)", err);
|
|
391
419
|
failures++;
|
|
392
420
|
}
|
|
393
421
|
}
|
package/test/unit.ts
CHANGED
|
@@ -1024,7 +1024,7 @@ await test("technical indicators: insufficient candles returns warning", async (
|
|
|
1024
1024
|
};
|
|
1025
1025
|
assertEqual(parsed.warning, "insufficient_data", "should return insufficient_data warning");
|
|
1026
1026
|
assertEqual(parsed.indicators, null, "indicators should be null");
|
|
1027
|
-
assertEqual(parsed.minimum_required,
|
|
1027
|
+
assertEqual(parsed.minimum_required, 20, "minimum_required should be 20");
|
|
1028
1028
|
} finally {
|
|
1029
1029
|
globalThis.fetch = savedFetch;
|
|
1030
1030
|
}
|