@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 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: '1h' (1 hour), '4h' (4 hours), or '1d' (1 day). Default: '1h'.",
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: '1h' (1 hour), '4h' (4 hours), or '1d' (1 day). Default: '1h'."),
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;;;;;;;;;;;;;;;;;;;;;CA6BtB,CAAC;AAsGF,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC3B,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"}
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 50 candles are available after aggregation, returns a structured warning instead. " +
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 = 50;
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()
@@ -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;AAQD;;;;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"}
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
@@ -19,6 +19,9 @@ export function getLiquidityRating(spreadPct) {
19
19
  return "low";
20
20
  }
21
21
  const PERIOD_MS = {
22
+ "5m": 5 * 60 * 1000,
23
+ "15m": 15 * 60 * 1000,
24
+ "30m": 30 * 60 * 1000,
22
25
  "1h": 60 * 60 * 1000,
23
26
  "4h": 4 * 60 * 60 * 1000,
24
27
  "1d": 24 * 60 * 60 * 1000,
@@ -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.0
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guiie/buda-mcp",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "mcpName": "io.github.gtorreal/buda-mcp",
5
5
  "description": "MCP server for Buda.com's public cryptocurrency exchange API (Chile, Colombia, Peru)",
6
6
  "type": "module",
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.1",
9
+ "version": "1.4.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@guiie/buda-mcp",
14
- "version": "1.4.1",
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: '1h' (1 hour), '4h' (4 hours), or '1d' (1 day). Default: '1h'.",
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: '1h' (1 hour), '4h' (4 hours), or '1d' (1 day). Default: '1h'."),
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 50 candles are available after aggregation, returns a structured warning instead. " +
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 = 50;
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
- "1h": 60 * 60 * 1000,
25
- "4h": 4 * 60 * 60 * 1000,
26
- "1d": 24 * 60 * 60 * 1000,
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
- section(`get_technical_indicators — ${TEST_MARKET} (1h, limit 1000)`);
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
- 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
- };
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
- pass("warning", `insufficient_data (${parsed.candles_available} candles available, need 50)`);
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("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");
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, 50, "minimum_required should be 50");
1027
+ assertEqual(parsed.minimum_required, 20, "minimum_required should be 20");
1028
1028
  } finally {
1029
1029
  globalThis.fetch = savedFetch;
1030
1030
  }