fixparser-plugin-mcp 9.1.7-3302db05 → 9.1.7-38cee007

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.
@@ -1,9 +1,51 @@
1
1
  // src/MCPLocal.ts
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { Fields as Fields3, MDEntryType as MDEntryType2, Messages as Messages3 } from "fixparser";
5
4
  import { z } from "zod";
6
5
 
6
+ // src/MCPBase.ts
7
+ var MCPBase = class {
8
+ /**
9
+ * Optional logger instance for diagnostics and output.
10
+ * @protected
11
+ */
12
+ logger;
13
+ /**
14
+ * FIXParser instance, set during plugin register().
15
+ * @protected
16
+ */
17
+ parser;
18
+ /**
19
+ * Called when server is setup and listening.
20
+ * @protected
21
+ */
22
+ onReady = void 0;
23
+ /**
24
+ * Map to store verified orders before execution
25
+ * @protected
26
+ */
27
+ verifiedOrders = /* @__PURE__ */ new Map();
28
+ /**
29
+ * Map to store pending market data requests
30
+ * @protected
31
+ */
32
+ pendingRequests = /* @__PURE__ */ new Map();
33
+ /**
34
+ * Map to store market data prices
35
+ * @protected
36
+ */
37
+ marketDataPrices = /* @__PURE__ */ new Map();
38
+ /**
39
+ * Maximum number of price history entries to keep per symbol
40
+ * @protected
41
+ */
42
+ MAX_PRICE_HISTORY = 1e5;
43
+ constructor({ logger, onReady }) {
44
+ this.logger = logger;
45
+ this.onReady = onReady;
46
+ }
47
+ };
48
+
7
49
  // src/schemas/schemas.ts
8
50
  var toolSchemas = {
9
51
  parse: {
@@ -87,7 +129,7 @@ var toolSchemas = {
87
129
  }
88
130
  },
89
131
  executeOrder: {
90
- description: "Executes a verified order. verifyOrder must be called before executeOrder. user has to explicitly allow executeOrder.",
132
+ description: "Executes a verified order. verifyOrder must be called before executeOrder.",
91
133
  schema: {
92
134
  type: "object",
93
135
  properties: {
@@ -231,19 +273,395 @@ var toolSchemas = {
231
273
  },
232
274
  required: ["symbol"]
233
275
  }
276
+ },
277
+ technicalAnalysis: {
278
+ description: "Performs comprehensive technical analysis on market data for a given symbol, including indicators like SMA, EMA, RSI, Bollinger Bands, and trading signals",
279
+ schema: {
280
+ type: "object",
281
+ properties: {
282
+ symbol: {
283
+ type: "string",
284
+ description: "The trading symbol to analyze (e.g., AAPL, MSFT, EURUSD)"
285
+ }
286
+ },
287
+ required: ["symbol"]
288
+ }
234
289
  }
235
290
  };
236
291
 
292
+ // src/tools/analytics.ts
293
+ function sum(numbers) {
294
+ return numbers.reduce((acc, val) => acc + val, 0);
295
+ }
296
+ var TechnicalAnalyzer = class {
297
+ prices;
298
+ volumes;
299
+ highs;
300
+ lows;
301
+ constructor(data) {
302
+ this.prices = data.map((d) => d.trade > 0 ? d.trade : d.midPrice);
303
+ this.volumes = data.map((d) => d.volume);
304
+ this.highs = data.map((d) => d.tradingSessionHighPrice > 0 ? d.tradingSessionHighPrice : d.trade);
305
+ this.lows = data.map((d) => d.tradingSessionLowPrice > 0 ? d.tradingSessionLowPrice : d.trade);
306
+ }
307
+ // Calculate Simple Moving Average
308
+ calculateSMA(data, period) {
309
+ const sma = [];
310
+ for (let i = period - 1; i < data.length; i++) {
311
+ const sum2 = data.slice(i - period + 1, i + 1).reduce((a, b) => a + b, 0);
312
+ sma.push(sum2 / period);
313
+ }
314
+ return sma;
315
+ }
316
+ // Calculate Exponential Moving Average
317
+ calculateEMA(data, period) {
318
+ const multiplier = 2 / (period + 1);
319
+ const ema = [data[0]];
320
+ for (let i = 1; i < data.length; i++) {
321
+ ema.push(data[i] * multiplier + ema[i - 1] * (1 - multiplier));
322
+ }
323
+ return ema;
324
+ }
325
+ // Calculate RSI
326
+ calculateRSI(data, period = 14) {
327
+ if (data.length < period + 1) return [];
328
+ const changes = [];
329
+ for (let i = 1; i < data.length; i++) {
330
+ changes.push(data[i] - data[i - 1]);
331
+ }
332
+ const gains = changes.map((change) => change > 0 ? change : 0);
333
+ const losses = changes.map((change) => change < 0 ? Math.abs(change) : 0);
334
+ let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period;
335
+ let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period;
336
+ const rsi = [];
337
+ for (let i = period; i < changes.length; i++) {
338
+ const rs = avgGain / avgLoss;
339
+ rsi.push(100 - 100 / (1 + rs));
340
+ avgGain = (avgGain * (period - 1) + gains[i]) / period;
341
+ avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
342
+ }
343
+ return rsi;
344
+ }
345
+ // Calculate Bollinger Bands
346
+ calculateBollingerBands(data, period = 20, stdDev = 2) {
347
+ if (data.length < period) return [];
348
+ const sma = this.calculateSMA(data, period);
349
+ const bands = [];
350
+ for (let i = 0; i < sma.length; i++) {
351
+ const dataSlice = data.slice(i, i + period);
352
+ const mean = sma[i];
353
+ const variance = dataSlice.reduce((sum2, price) => sum2 + (price - mean) ** 2, 0) / period;
354
+ const standardDeviation = Math.sqrt(variance);
355
+ bands.push({
356
+ upper: mean + standardDeviation * stdDev,
357
+ middle: mean,
358
+ lower: mean - standardDeviation * stdDev
359
+ });
360
+ }
361
+ return bands;
362
+ }
363
+ // Calculate maximum drawdown
364
+ calculateMaxDrawdown(prices) {
365
+ let maxPrice = prices[0];
366
+ let maxDrawdown = 0;
367
+ for (let i = 1; i < prices.length; i++) {
368
+ if (prices[i] > maxPrice) {
369
+ maxPrice = prices[i];
370
+ }
371
+ const drawdown = (maxPrice - prices[i]) / maxPrice;
372
+ if (drawdown > maxDrawdown) {
373
+ maxDrawdown = drawdown;
374
+ }
375
+ }
376
+ return maxDrawdown;
377
+ }
378
+ // Calculate price changes for volatility
379
+ calculatePriceChanges() {
380
+ const changes = [];
381
+ for (let i = 1; i < this.prices.length; i++) {
382
+ changes.push((this.prices[i] - this.prices[i - 1]) / this.prices[i - 1]);
383
+ }
384
+ return changes;
385
+ }
386
+ // Generate comprehensive market analysis
387
+ analyze() {
388
+ const currentPrice = this.prices[this.prices.length - 1];
389
+ const startPrice = this.prices[0];
390
+ const sessionHigh = Math.max(...this.highs);
391
+ const sessionLow = Math.min(...this.lows);
392
+ const totalVolume = sum(this.volumes);
393
+ const avgVolume = totalVolume / this.volumes.length;
394
+ const priceChanges = this.calculatePriceChanges();
395
+ const volatility = priceChanges.length > 0 ? Math.sqrt(
396
+ priceChanges.reduce((sum2, change) => sum2 + change ** 2, 0) / priceChanges.length
397
+ ) * Math.sqrt(252) * 100 : 0;
398
+ const sessionReturn = (currentPrice - startPrice) / startPrice * 100;
399
+ const pricePosition = (currentPrice - sessionLow) / (sessionHigh - sessionLow) * 100;
400
+ const trueVWAP = this.prices.reduce((sum2, price, i) => sum2 + price * this.volumes[i], 0) / totalVolume;
401
+ const momentum5 = this.prices.length > 5 ? (currentPrice - this.prices[Math.max(0, this.prices.length - 6)]) / this.prices[Math.max(0, this.prices.length - 6)] * 100 : 0;
402
+ const momentum10 = this.prices.length > 10 ? (currentPrice - this.prices[Math.max(0, this.prices.length - 11)]) / this.prices[Math.max(0, this.prices.length - 11)] * 100 : 0;
403
+ const maxDrawdown = this.calculateMaxDrawdown(this.prices);
404
+ return {
405
+ currentPrice,
406
+ startPrice,
407
+ sessionHigh,
408
+ sessionLow,
409
+ totalVolume,
410
+ avgVolume,
411
+ volatility,
412
+ sessionReturn,
413
+ pricePosition,
414
+ trueVWAP,
415
+ momentum5,
416
+ momentum10,
417
+ maxDrawdown
418
+ };
419
+ }
420
+ // Generate technical indicators
421
+ getTechnicalIndicators() {
422
+ return {
423
+ sma5: this.calculateSMA(this.prices, 5),
424
+ sma10: this.calculateSMA(this.prices, 10),
425
+ ema8: this.calculateEMA(this.prices, 8),
426
+ ema21: this.calculateEMA(this.prices, 21),
427
+ rsi: this.calculateRSI(this.prices, 14),
428
+ bollinger: this.calculateBollingerBands(this.prices, 20, 2)
429
+ };
430
+ }
431
+ // Generate trading signals
432
+ generateSignals() {
433
+ const analysis = this.analyze();
434
+ let bullishSignals = 0;
435
+ let bearishSignals = 0;
436
+ const signals = [];
437
+ if (analysis.currentPrice > analysis.trueVWAP) {
438
+ signals.push(
439
+ `\u2713 BULLISH: Price above VWAP (+${((analysis.currentPrice - analysis.trueVWAP) / analysis.trueVWAP * 100).toFixed(2)}%)`
440
+ );
441
+ bullishSignals++;
442
+ } else {
443
+ signals.push(
444
+ `\u2717 BEARISH: Price below VWAP (${((analysis.currentPrice - analysis.trueVWAP) / analysis.trueVWAP * 100).toFixed(2)}%)`
445
+ );
446
+ bearishSignals++;
447
+ }
448
+ if (analysis.momentum5 > 0 && analysis.momentum10 > 0) {
449
+ signals.push("\u2713 BULLISH: Positive momentum on both timeframes");
450
+ bullishSignals++;
451
+ } else if (analysis.momentum5 < 0 && analysis.momentum10 < 0) {
452
+ signals.push("\u2717 BEARISH: Negative momentum on both timeframes");
453
+ bearishSignals++;
454
+ } else {
455
+ signals.push("\u25D0 MIXED: Conflicting momentum signals");
456
+ }
457
+ const currentVolume = this.volumes[this.volumes.length - 1];
458
+ const volumeRatio = currentVolume / analysis.avgVolume;
459
+ if (volumeRatio > 1.2 && analysis.sessionReturn > 0) {
460
+ signals.push("\u2713 BULLISH: Above-average volume supporting upward move");
461
+ bullishSignals++;
462
+ } else if (volumeRatio > 1.2 && analysis.sessionReturn < 0) {
463
+ signals.push("\u2717 BEARISH: Above-average volume supporting downward move");
464
+ bearishSignals++;
465
+ } else {
466
+ signals.push("\u25D0 NEUTRAL: Volume not providing clear direction");
467
+ }
468
+ if (analysis.pricePosition > 65 && analysis.volatility > 30) {
469
+ signals.push("\u2717 BEARISH: High in range with elevated volatility - reversal risk");
470
+ bearishSignals++;
471
+ } else if (analysis.pricePosition < 35 && analysis.volatility > 30) {
472
+ signals.push("\u2713 BULLISH: Low in range with volatility - potential bounce");
473
+ bullishSignals++;
474
+ } else {
475
+ signals.push("\u25D0 NEUTRAL: Price position and volatility not extreme");
476
+ }
477
+ return { bullishSignals, bearishSignals, signals };
478
+ }
479
+ // Generate comprehensive JSON analysis
480
+ generateJSONAnalysis(symbol) {
481
+ const analysis = this.analyze();
482
+ const indicators = this.getTechnicalIndicators();
483
+ const signals = this.generateSignals();
484
+ const currentSMA5 = indicators.sma5.length > 0 ? indicators.sma5[indicators.sma5.length - 1] : null;
485
+ const currentSMA10 = indicators.sma10.length > 0 ? indicators.sma10[indicators.sma10.length - 1] : null;
486
+ const currentEMA8 = indicators.ema8[indicators.ema8.length - 1];
487
+ const currentEMA21 = indicators.ema21[indicators.ema21.length - 1];
488
+ const currentRSI = indicators.rsi.length > 0 ? indicators.rsi[indicators.rsi.length - 1] : null;
489
+ const currentBB = indicators.bollinger.length > 0 ? indicators.bollinger[indicators.bollinger.length - 1] : null;
490
+ const currentVolume = this.volumes[this.volumes.length - 1];
491
+ const volumeRatio = currentVolume / analysis.avgVolume;
492
+ const currentDrawdown = (analysis.sessionHigh - analysis.currentPrice) / analysis.sessionHigh * 100;
493
+ const rangeWidth = (analysis.sessionHigh - analysis.sessionLow) / analysis.sessionLow * 100;
494
+ const priceVsVWAP = (analysis.currentPrice - analysis.trueVWAP) / analysis.trueVWAP * 100;
495
+ const totalScore = signals.bullishSignals - signals.bearishSignals;
496
+ const overallSignal = totalScore > 0 ? "BULLISH_BIAS" : totalScore < 0 ? "BEARISH_BIAS" : "NEUTRAL";
497
+ const targetEntry = Math.max(analysis.sessionLow * 1.005, analysis.trueVWAP * 0.998);
498
+ const stopLoss = analysis.sessionLow * 0.995;
499
+ const profitTarget = analysis.sessionHigh * 0.995;
500
+ const riskRewardRatio = (profitTarget - analysis.currentPrice) / (analysis.currentPrice - stopLoss);
501
+ return {
502
+ symbol,
503
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
504
+ marketStructure: {
505
+ currentPrice: analysis.currentPrice,
506
+ startPrice: analysis.startPrice,
507
+ sessionHigh: analysis.sessionHigh,
508
+ sessionLow: analysis.sessionLow,
509
+ rangeWidth,
510
+ totalVolume: analysis.totalVolume,
511
+ sessionPerformance: analysis.sessionReturn,
512
+ positionInRange: analysis.pricePosition
513
+ },
514
+ volatility: {
515
+ impliedVolatility: analysis.volatility,
516
+ maxDrawdown: analysis.maxDrawdown * 100,
517
+ currentDrawdown
518
+ },
519
+ technicalIndicators: {
520
+ sma5: currentSMA5,
521
+ sma10: currentSMA10,
522
+ ema8: currentEMA8,
523
+ ema21: currentEMA21,
524
+ rsi: currentRSI,
525
+ bollingerBands: currentBB ? {
526
+ upper: currentBB.upper,
527
+ middle: currentBB.middle,
528
+ lower: currentBB.lower,
529
+ position: (analysis.currentPrice - currentBB.lower) / (currentBB.upper - currentBB.lower) * 100
530
+ } : null
531
+ },
532
+ volumeAnalysis: {
533
+ currentVolume,
534
+ averageVolume: Math.round(analysis.avgVolume),
535
+ volumeRatio,
536
+ trueVWAP: analysis.trueVWAP,
537
+ priceVsVWAP
538
+ },
539
+ momentum: {
540
+ momentum5: analysis.momentum5,
541
+ momentum10: analysis.momentum10,
542
+ sessionROC: analysis.sessionReturn
543
+ },
544
+ tradingSignals: {
545
+ ...signals,
546
+ overallSignal,
547
+ signalScore: totalScore
548
+ },
549
+ riskManagement: {
550
+ targetEntry,
551
+ stopLoss,
552
+ profitTarget,
553
+ riskRewardRatio
554
+ }
555
+ };
556
+ }
557
+ };
558
+ var createTechnicalAnalysisHandler = (marketDataPrices) => {
559
+ return async (args) => {
560
+ try {
561
+ const symbol = args.symbol;
562
+ const priceHistory = marketDataPrices.get(symbol) || [];
563
+ if (priceHistory.length === 0) {
564
+ return {
565
+ content: [
566
+ {
567
+ type: "text",
568
+ text: `No price data available for ${symbol}. Please request market data first.`,
569
+ uri: "technicalAnalysis"
570
+ }
571
+ ]
572
+ };
573
+ }
574
+ const analyzer = new TechnicalAnalyzer(priceHistory);
575
+ const analysis = analyzer.generateJSONAnalysis(symbol);
576
+ return {
577
+ content: [
578
+ {
579
+ type: "text",
580
+ text: `Technical Analysis for ${symbol}:
581
+
582
+ ${JSON.stringify(analysis, null, 2)}`,
583
+ uri: "technicalAnalysis"
584
+ }
585
+ ]
586
+ };
587
+ } catch (error) {
588
+ return {
589
+ content: [
590
+ {
591
+ type: "text",
592
+ text: `Error performing technical analysis: ${error instanceof Error ? error.message : "Unknown error"}`,
593
+ uri: "technicalAnalysis"
594
+ }
595
+ ],
596
+ isError: true
597
+ };
598
+ }
599
+ };
600
+ };
601
+
237
602
  // src/tools/marketData.ts
238
603
  import { Field, Fields, MDEntryType, Messages } from "fixparser";
239
604
  import QuickChart from "quickchart-js";
240
605
  var createMarketDataRequestHandler = (parser, pendingRequests) => {
241
606
  return async (args) => {
242
607
  try {
608
+ parser.logger.log({
609
+ level: "info",
610
+ message: `Sending market data request for symbols: ${args.symbols.join(", ")}`
611
+ });
243
612
  const response = new Promise((resolve) => {
244
613
  pendingRequests.set(args.mdReqID, resolve);
614
+ parser.logger.log({
615
+ level: "info",
616
+ message: `Registered callback for market data request ID: ${args.mdReqID}`
617
+ });
245
618
  });
246
- const entryTypes = args.mdEntryTypes || [MDEntryType.Bid, MDEntryType.Offer, MDEntryType.TradeVolume];
619
+ const entryTypes = args.mdEntryTypes || [
620
+ MDEntryType.Bid,
621
+ MDEntryType.Offer,
622
+ MDEntryType.Trade,
623
+ MDEntryType.IndexValue,
624
+ MDEntryType.OpeningPrice,
625
+ MDEntryType.ClosingPrice,
626
+ MDEntryType.SettlementPrice,
627
+ MDEntryType.TradingSessionHighPrice,
628
+ MDEntryType.TradingSessionLowPrice,
629
+ MDEntryType.VWAP,
630
+ MDEntryType.Imbalance,
631
+ MDEntryType.TradeVolume,
632
+ MDEntryType.OpenInterest,
633
+ MDEntryType.CompositeUnderlyingPrice,
634
+ MDEntryType.SimulatedSellPrice,
635
+ MDEntryType.SimulatedBuyPrice,
636
+ MDEntryType.MarginRate,
637
+ MDEntryType.MidPrice,
638
+ MDEntryType.EmptyBook,
639
+ MDEntryType.SettleHighPrice,
640
+ MDEntryType.SettleLowPrice,
641
+ MDEntryType.PriorSettlePrice,
642
+ MDEntryType.SessionHighBid,
643
+ MDEntryType.SessionLowOffer,
644
+ MDEntryType.EarlyPrices,
645
+ MDEntryType.AuctionClearingPrice,
646
+ MDEntryType.SwapValueFactor,
647
+ MDEntryType.DailyValueAdjustmentForLongPositions,
648
+ MDEntryType.CumulativeValueAdjustmentForLongPositions,
649
+ MDEntryType.DailyValueAdjustmentForShortPositions,
650
+ MDEntryType.CumulativeValueAdjustmentForShortPositions,
651
+ MDEntryType.FixingPrice,
652
+ MDEntryType.CashRate,
653
+ MDEntryType.RecoveryRate,
654
+ MDEntryType.RecoveryRateForLong,
655
+ MDEntryType.RecoveryRateForShort,
656
+ MDEntryType.MarketBid,
657
+ MDEntryType.MarketOffer,
658
+ MDEntryType.ShortSaleMinPrice,
659
+ MDEntryType.PreviousClosingPrice,
660
+ MDEntryType.ThresholdLimitPriceBanding,
661
+ MDEntryType.DailyFinancingValue,
662
+ MDEntryType.AccruedFinancingValue,
663
+ MDEntryType.TWAP
664
+ ];
247
665
  const messageFields = [
248
666
  new Field(Fields.MsgType, Messages.MarketDataRequest),
249
667
  new Field(Fields.SenderCompID, parser.sender),
@@ -265,6 +683,10 @@ var createMarketDataRequestHandler = (parser, pendingRequests) => {
265
683
  });
266
684
  const mdr = parser.createMessage(...messageFields);
267
685
  if (!parser.connected) {
686
+ parser.logger.log({
687
+ level: "error",
688
+ message: "Not connected. Cannot send market data request."
689
+ });
268
690
  return {
269
691
  content: [
270
692
  {
@@ -276,8 +698,16 @@ var createMarketDataRequestHandler = (parser, pendingRequests) => {
276
698
  isError: true
277
699
  };
278
700
  }
701
+ parser.logger.log({
702
+ level: "info",
703
+ message: `Sending market data request message: ${JSON.stringify(mdr?.toFIXJSON())}`
704
+ });
279
705
  parser.send(mdr);
280
706
  const fixData = await response;
707
+ parser.logger.log({
708
+ level: "info",
709
+ message: `Received market data response for request ID: ${args.mdReqID}`
710
+ });
281
711
  return {
282
712
  content: [
283
713
  {
@@ -326,6 +756,9 @@ var createGetStockGraphHandler = (marketDataPrices) => {
326
756
  const offerData = priceHistory.map((point) => point.offer);
327
757
  const spreadData = priceHistory.map((point) => point.spread);
328
758
  const volumeData = priceHistory.map((point) => point.volume);
759
+ const tradeData = priceHistory.map((point) => point.trade);
760
+ const vwapData = priceHistory.map((point) => point.vwap);
761
+ const twapData = priceHistory.map((point) => point.twap);
329
762
  const config = {
330
763
  type: "line",
331
764
  data: {
@@ -355,6 +788,30 @@ var createGetStockGraphHandler = (marketDataPrices) => {
355
788
  fill: false,
356
789
  tension: 0.4
357
790
  },
791
+ {
792
+ label: "Trade",
793
+ data: tradeData,
794
+ borderColor: "#ffc107",
795
+ backgroundColor: "rgba(255, 193, 7, 0.1)",
796
+ fill: false,
797
+ tension: 0.4
798
+ },
799
+ {
800
+ label: "VWAP",
801
+ data: vwapData,
802
+ borderColor: "#17a2b8",
803
+ backgroundColor: "rgba(23, 162, 184, 0.1)",
804
+ fill: false,
805
+ tension: 0.4
806
+ },
807
+ {
808
+ label: "TWAP",
809
+ data: twapData,
810
+ borderColor: "#6610f2",
811
+ backgroundColor: "rgba(102, 16, 242, 0.1)",
812
+ fill: false,
813
+ tension: 0.4
814
+ },
358
815
  {
359
816
  label: "Volume",
360
817
  data: volumeData,
@@ -400,7 +857,7 @@ var createGetStockGraphHandler = (marketDataPrices) => {
400
857
  content: [
401
858
  {
402
859
  type: "text",
403
- text: `Error: ${error instanceof Error ? error.message : "Failed to generate chart"}`,
860
+ text: `Error: ${error instanceof Error ? error.message : "Failed to generate graph"}`,
404
861
  uri: "getStockGraph"
405
862
  }
406
863
  ],
@@ -438,7 +895,48 @@ var createGetStockPriceHistoryHandler = (marketDataPrices) => {
438
895
  bid: point.bid,
439
896
  offer: point.offer,
440
897
  spread: point.spread,
441
- volume: point.volume
898
+ volume: point.volume,
899
+ trade: point.trade,
900
+ indexValue: point.indexValue,
901
+ openingPrice: point.openingPrice,
902
+ closingPrice: point.closingPrice,
903
+ settlementPrice: point.settlementPrice,
904
+ tradingSessionHighPrice: point.tradingSessionHighPrice,
905
+ tradingSessionLowPrice: point.tradingSessionLowPrice,
906
+ vwap: point.vwap,
907
+ imbalance: point.imbalance,
908
+ openInterest: point.openInterest,
909
+ compositeUnderlyingPrice: point.compositeUnderlyingPrice,
910
+ simulatedSellPrice: point.simulatedSellPrice,
911
+ simulatedBuyPrice: point.simulatedBuyPrice,
912
+ marginRate: point.marginRate,
913
+ midPrice: point.midPrice,
914
+ emptyBook: point.emptyBook,
915
+ settleHighPrice: point.settleHighPrice,
916
+ settleLowPrice: point.settleLowPrice,
917
+ priorSettlePrice: point.priorSettlePrice,
918
+ sessionHighBid: point.sessionHighBid,
919
+ sessionLowOffer: point.sessionLowOffer,
920
+ earlyPrices: point.earlyPrices,
921
+ auctionClearingPrice: point.auctionClearingPrice,
922
+ swapValueFactor: point.swapValueFactor,
923
+ dailyValueAdjustmentForLongPositions: point.dailyValueAdjustmentForLongPositions,
924
+ cumulativeValueAdjustmentForLongPositions: point.cumulativeValueAdjustmentForLongPositions,
925
+ dailyValueAdjustmentForShortPositions: point.dailyValueAdjustmentForShortPositions,
926
+ cumulativeValueAdjustmentForShortPositions: point.cumulativeValueAdjustmentForShortPositions,
927
+ fixingPrice: point.fixingPrice,
928
+ cashRate: point.cashRate,
929
+ recoveryRate: point.recoveryRate,
930
+ recoveryRateForLong: point.recoveryRateForLong,
931
+ recoveryRateForShort: point.recoveryRateForShort,
932
+ marketBid: point.marketBid,
933
+ marketOffer: point.marketOffer,
934
+ shortSaleMinPrice: point.shortSaleMinPrice,
935
+ previousClosingPrice: point.previousClosingPrice,
936
+ thresholdLimitPriceBanding: point.thresholdLimitPriceBanding,
937
+ dailyFinancingValue: point.dailyFinancingValue,
938
+ accruedFinancingValue: point.accruedFinancingValue,
939
+ twap: point.twap
442
940
  }))
443
941
  },
444
942
  null,
@@ -453,7 +951,7 @@ var createGetStockPriceHistoryHandler = (marketDataPrices) => {
453
951
  content: [
454
952
  {
455
953
  type: "text",
456
- text: `Error: ${error instanceof Error ? error.message : "Failed to get stock price history"}`,
954
+ text: `Error: ${error instanceof Error ? error.message : "Failed to get price history"}`,
457
955
  uri: "getStockPriceHistory"
458
956
  }
459
957
  ],
@@ -561,7 +1059,7 @@ Parameters verified:
561
1059
  - Symbol: ${args.symbol}
562
1060
  - TimeInForce: ${args.timeInForce} (${timeInForceNames[args.timeInForce]})
563
1061
 
564
- To execute this order, call the executeOrder tool with these exact same parameters.`,
1062
+ To execute this order, call the executeOrder tool with these exact same parameters. Important: The user has to explicitly confirm before executeOrder is called!`,
565
1063
  uri: "verifyOrder"
566
1064
  }
567
1065
  ]
@@ -757,12 +1255,259 @@ var createToolHandlers = (parser, verifiedOrders, pendingRequests, marketDataPri
757
1255
  executeOrder: createExecuteOrderHandler(parser, verifiedOrders, pendingRequests),
758
1256
  marketDataRequest: createMarketDataRequestHandler(parser, pendingRequests),
759
1257
  getStockGraph: createGetStockGraphHandler(marketDataPrices),
760
- getStockPriceHistory: createGetStockPriceHistoryHandler(marketDataPrices)
1258
+ getStockPriceHistory: createGetStockPriceHistoryHandler(marketDataPrices),
1259
+ technicalAnalysis: createTechnicalAnalysisHandler(marketDataPrices)
761
1260
  });
762
1261
 
1262
+ // src/utils/messageHandler.ts
1263
+ import { Fields as Fields3, MDEntryType as MDEntryType2, Messages as Messages3 } from "fixparser";
1264
+ function getEnumValue(enumObj, name) {
1265
+ return enumObj[name] || name;
1266
+ }
1267
+ function handleMessage(message, parser, pendingRequests, marketDataPrices, maxPriceHistory, onPriceUpdate) {
1268
+ const msgType = message.messageType;
1269
+ if (msgType === Messages3.MarketDataSnapshotFullRefresh || msgType === Messages3.MarketDataIncrementalRefresh) {
1270
+ const symbol = message.getField(Fields3.Symbol)?.value;
1271
+ const fixJson = message.toFIXJSON();
1272
+ const entries = fixJson.Body?.NoMDEntries || [];
1273
+ const data = {
1274
+ timestamp: Date.now(),
1275
+ bid: 0,
1276
+ offer: 0,
1277
+ spread: 0,
1278
+ volume: 0,
1279
+ trade: 0,
1280
+ indexValue: 0,
1281
+ openingPrice: 0,
1282
+ closingPrice: 0,
1283
+ settlementPrice: 0,
1284
+ tradingSessionHighPrice: 0,
1285
+ tradingSessionLowPrice: 0,
1286
+ vwap: 0,
1287
+ imbalance: 0,
1288
+ openInterest: 0,
1289
+ compositeUnderlyingPrice: 0,
1290
+ simulatedSellPrice: 0,
1291
+ simulatedBuyPrice: 0,
1292
+ marginRate: 0,
1293
+ midPrice: 0,
1294
+ emptyBook: 0,
1295
+ settleHighPrice: 0,
1296
+ settleLowPrice: 0,
1297
+ priorSettlePrice: 0,
1298
+ sessionHighBid: 0,
1299
+ sessionLowOffer: 0,
1300
+ earlyPrices: 0,
1301
+ auctionClearingPrice: 0,
1302
+ swapValueFactor: 0,
1303
+ dailyValueAdjustmentForLongPositions: 0,
1304
+ cumulativeValueAdjustmentForLongPositions: 0,
1305
+ dailyValueAdjustmentForShortPositions: 0,
1306
+ cumulativeValueAdjustmentForShortPositions: 0,
1307
+ fixingPrice: 0,
1308
+ cashRate: 0,
1309
+ recoveryRate: 0,
1310
+ recoveryRateForLong: 0,
1311
+ recoveryRateForShort: 0,
1312
+ marketBid: 0,
1313
+ marketOffer: 0,
1314
+ shortSaleMinPrice: 0,
1315
+ previousClosingPrice: 0,
1316
+ thresholdLimitPriceBanding: 0,
1317
+ dailyFinancingValue: 0,
1318
+ accruedFinancingValue: 0,
1319
+ twap: 0
1320
+ };
1321
+ for (const entry of entries) {
1322
+ const entryType = entry.MDEntryType;
1323
+ const price = entry.MDEntryPx ? Number.parseFloat(entry.MDEntryPx) : 0;
1324
+ const size = entry.MDEntrySize ? Number.parseFloat(entry.MDEntrySize) : 0;
1325
+ const enumValue = getEnumValue(MDEntryType2, entryType);
1326
+ switch (enumValue) {
1327
+ case MDEntryType2.Bid:
1328
+ data.bid = price;
1329
+ break;
1330
+ case MDEntryType2.Offer:
1331
+ data.offer = price;
1332
+ break;
1333
+ case MDEntryType2.Trade:
1334
+ data.trade = price;
1335
+ break;
1336
+ case MDEntryType2.IndexValue:
1337
+ data.indexValue = price;
1338
+ break;
1339
+ case MDEntryType2.OpeningPrice:
1340
+ data.openingPrice = price;
1341
+ break;
1342
+ case MDEntryType2.ClosingPrice:
1343
+ data.closingPrice = price;
1344
+ break;
1345
+ case MDEntryType2.SettlementPrice:
1346
+ data.settlementPrice = price;
1347
+ break;
1348
+ case MDEntryType2.TradingSessionHighPrice:
1349
+ data.tradingSessionHighPrice = price;
1350
+ break;
1351
+ case MDEntryType2.TradingSessionLowPrice:
1352
+ data.tradingSessionLowPrice = price;
1353
+ break;
1354
+ case MDEntryType2.VWAP:
1355
+ data.vwap = price;
1356
+ break;
1357
+ case MDEntryType2.Imbalance:
1358
+ data.imbalance = size;
1359
+ break;
1360
+ case MDEntryType2.TradeVolume:
1361
+ data.volume = size;
1362
+ break;
1363
+ case MDEntryType2.OpenInterest:
1364
+ data.openInterest = size;
1365
+ break;
1366
+ case MDEntryType2.CompositeUnderlyingPrice:
1367
+ data.compositeUnderlyingPrice = price;
1368
+ break;
1369
+ case MDEntryType2.SimulatedSellPrice:
1370
+ data.simulatedSellPrice = price;
1371
+ break;
1372
+ case MDEntryType2.SimulatedBuyPrice:
1373
+ data.simulatedBuyPrice = price;
1374
+ break;
1375
+ case MDEntryType2.MarginRate:
1376
+ data.marginRate = price;
1377
+ break;
1378
+ case MDEntryType2.MidPrice:
1379
+ data.midPrice = price;
1380
+ break;
1381
+ case MDEntryType2.EmptyBook:
1382
+ data.emptyBook = 1;
1383
+ break;
1384
+ case MDEntryType2.SettleHighPrice:
1385
+ data.settleHighPrice = price;
1386
+ break;
1387
+ case MDEntryType2.SettleLowPrice:
1388
+ data.settleLowPrice = price;
1389
+ break;
1390
+ case MDEntryType2.PriorSettlePrice:
1391
+ data.priorSettlePrice = price;
1392
+ break;
1393
+ case MDEntryType2.SessionHighBid:
1394
+ data.sessionHighBid = price;
1395
+ break;
1396
+ case MDEntryType2.SessionLowOffer:
1397
+ data.sessionLowOffer = price;
1398
+ break;
1399
+ case MDEntryType2.EarlyPrices:
1400
+ data.earlyPrices = price;
1401
+ break;
1402
+ case MDEntryType2.AuctionClearingPrice:
1403
+ data.auctionClearingPrice = price;
1404
+ break;
1405
+ case MDEntryType2.SwapValueFactor:
1406
+ data.swapValueFactor = price;
1407
+ break;
1408
+ case MDEntryType2.DailyValueAdjustmentForLongPositions:
1409
+ data.dailyValueAdjustmentForLongPositions = price;
1410
+ break;
1411
+ case MDEntryType2.CumulativeValueAdjustmentForLongPositions:
1412
+ data.cumulativeValueAdjustmentForLongPositions = price;
1413
+ break;
1414
+ case MDEntryType2.DailyValueAdjustmentForShortPositions:
1415
+ data.dailyValueAdjustmentForShortPositions = price;
1416
+ break;
1417
+ case MDEntryType2.CumulativeValueAdjustmentForShortPositions:
1418
+ data.cumulativeValueAdjustmentForShortPositions = price;
1419
+ break;
1420
+ case MDEntryType2.FixingPrice:
1421
+ data.fixingPrice = price;
1422
+ break;
1423
+ case MDEntryType2.CashRate:
1424
+ data.cashRate = price;
1425
+ break;
1426
+ case MDEntryType2.RecoveryRate:
1427
+ data.recoveryRate = price;
1428
+ break;
1429
+ case MDEntryType2.RecoveryRateForLong:
1430
+ data.recoveryRateForLong = price;
1431
+ break;
1432
+ case MDEntryType2.RecoveryRateForShort:
1433
+ data.recoveryRateForShort = price;
1434
+ break;
1435
+ case MDEntryType2.MarketBid:
1436
+ data.marketBid = price;
1437
+ break;
1438
+ case MDEntryType2.MarketOffer:
1439
+ data.marketOffer = price;
1440
+ break;
1441
+ case MDEntryType2.ShortSaleMinPrice:
1442
+ data.shortSaleMinPrice = price;
1443
+ break;
1444
+ case MDEntryType2.PreviousClosingPrice:
1445
+ data.previousClosingPrice = price;
1446
+ break;
1447
+ case MDEntryType2.ThresholdLimitPriceBanding:
1448
+ data.thresholdLimitPriceBanding = price;
1449
+ break;
1450
+ case MDEntryType2.DailyFinancingValue:
1451
+ data.dailyFinancingValue = price;
1452
+ break;
1453
+ case MDEntryType2.AccruedFinancingValue:
1454
+ data.accruedFinancingValue = price;
1455
+ break;
1456
+ case MDEntryType2.TWAP:
1457
+ data.twap = price;
1458
+ break;
1459
+ }
1460
+ }
1461
+ data.spread = data.offer - data.bid;
1462
+ if (!marketDataPrices.has(symbol)) {
1463
+ marketDataPrices.set(symbol, []);
1464
+ }
1465
+ const prices = marketDataPrices.get(symbol);
1466
+ prices.push(data);
1467
+ if (prices.length > maxPriceHistory) {
1468
+ prices.splice(0, prices.length - maxPriceHistory);
1469
+ }
1470
+ onPriceUpdate?.(symbol, data);
1471
+ const mdReqID = message.getField(Fields3.MDReqID)?.value;
1472
+ if (mdReqID) {
1473
+ const callback = pendingRequests.get(mdReqID);
1474
+ if (callback) {
1475
+ callback(message);
1476
+ pendingRequests.delete(mdReqID);
1477
+ }
1478
+ }
1479
+ } else if (msgType === Messages3.ExecutionReport) {
1480
+ const reqId = message.getField(Fields3.ClOrdID)?.value;
1481
+ const callback = pendingRequests.get(reqId);
1482
+ if (callback) {
1483
+ callback(message);
1484
+ pendingRequests.delete(reqId);
1485
+ }
1486
+ }
1487
+ }
1488
+
763
1489
  // src/MCPLocal.ts
764
- var MCPLocal = class {
765
- parser;
1490
+ var MCPLocal = class extends MCPBase {
1491
+ /**
1492
+ * Map to store verified orders before execution
1493
+ * @private
1494
+ */
1495
+ verifiedOrders = /* @__PURE__ */ new Map();
1496
+ /**
1497
+ * Map to store pending requests and their callbacks
1498
+ * @private
1499
+ */
1500
+ pendingRequests = /* @__PURE__ */ new Map();
1501
+ /**
1502
+ * Map to store market data prices for each symbol
1503
+ * @private
1504
+ */
1505
+ marketDataPrices = /* @__PURE__ */ new Map();
1506
+ /**
1507
+ * Maximum number of price history entries to keep per symbol
1508
+ * @private
1509
+ */
1510
+ MAX_PRICE_HISTORY = 1e5;
766
1511
  server = new Server(
767
1512
  {
768
1513
  name: "fixparser",
@@ -784,90 +1529,13 @@ var MCPLocal = class {
784
1529
  }
785
1530
  );
786
1531
  transport = new StdioServerTransport();
787
- onReady = void 0;
788
- pendingRequests = /* @__PURE__ */ new Map();
789
- verifiedOrders = /* @__PURE__ */ new Map();
790
- marketDataPrices = /* @__PURE__ */ new Map();
791
- MAX_PRICE_HISTORY = 1e5;
792
- // Maximum number of price points to store per symbol
793
1532
  constructor({ logger, onReady }) {
794
- if (onReady) this.onReady = onReady;
1533
+ super({ logger, onReady });
795
1534
  }
796
1535
  async register(parser) {
797
1536
  this.parser = parser;
798
1537
  this.parser.addOnMessageCallback((message) => {
799
- this.parser?.logger.log({
800
- level: "info",
801
- message: `MCP Server received message: ${message.messageType}: ${message.description}`
802
- });
803
- const msgType = message.messageType;
804
- if (msgType === Messages3.MarketDataSnapshotFullRefresh || msgType === Messages3.ExecutionReport || msgType === Messages3.Reject || msgType === Messages3.MarketDataIncrementalRefresh) {
805
- this.parser?.logger.log({
806
- level: "info",
807
- message: `MCP Server handling message type: ${msgType}`
808
- });
809
- let id;
810
- if (msgType === Messages3.MarketDataIncrementalRefresh || msgType === Messages3.MarketDataSnapshotFullRefresh) {
811
- const symbol = message.getField(Fields3.Symbol);
812
- const entryType = message.getField(Fields3.MDEntryType);
813
- const price = message.getField(Fields3.MDEntryPx);
814
- const size = message.getField(Fields3.MDEntrySize);
815
- const timestamp = message.getField(Fields3.MDEntryTime)?.value || Date.now();
816
- if (symbol?.value && price?.value) {
817
- const symbolStr = String(symbol.value);
818
- const priceNum = Number(price.value);
819
- const sizeNum = size?.value ? Number(size.value) : 0;
820
- const priceHistory = this.marketDataPrices.get(symbolStr) || [];
821
- const lastEntry = priceHistory[priceHistory.length - 1] || {
822
- timestamp: 0,
823
- bid: 0,
824
- offer: 0,
825
- spread: 0,
826
- volume: 0
827
- };
828
- const newEntry = {
829
- timestamp: Number(timestamp),
830
- bid: entryType?.value === MDEntryType2.Bid ? priceNum : lastEntry.bid,
831
- offer: entryType?.value === MDEntryType2.Offer ? priceNum : lastEntry.offer,
832
- spread: entryType?.value === MDEntryType2.Offer ? priceNum - lastEntry.bid : entryType?.value === MDEntryType2.Bid ? lastEntry.offer - priceNum : lastEntry.spread,
833
- volume: entryType?.value === MDEntryType2.TradeVolume ? sizeNum : lastEntry.volume
834
- };
835
- priceHistory.push(newEntry);
836
- if (priceHistory.length > this.MAX_PRICE_HISTORY) {
837
- priceHistory.shift();
838
- }
839
- this.marketDataPrices.set(symbolStr, priceHistory);
840
- this.parser?.logger.log({
841
- level: "info",
842
- message: `MCP Server added ${symbol}: ${JSON.stringify(newEntry)}`
843
- });
844
- this.server.notification({
845
- method: "priceUpdate",
846
- params: {
847
- symbol: symbolStr,
848
- ...newEntry
849
- }
850
- });
851
- }
852
- }
853
- if (msgType === Messages3.MarketDataSnapshotFullRefresh) {
854
- const mdReqID = message.getField(Fields3.MDReqID);
855
- if (mdReqID) id = String(mdReqID.value);
856
- } else if (msgType === Messages3.ExecutionReport) {
857
- const clOrdID = message.getField(Fields3.ClOrdID);
858
- if (clOrdID) id = String(clOrdID.value);
859
- } else if (msgType === Messages3.Reject) {
860
- const refSeqNum = message.getField(Fields3.RefSeqNum);
861
- if (refSeqNum) id = String(refSeqNum.value);
862
- }
863
- if (id) {
864
- const callback = this.pendingRequests.get(id);
865
- if (callback) {
866
- callback(message);
867
- this.pendingRequests.delete(id);
868
- }
869
- }
870
- }
1538
+ handleMessage(message, this.parser, this.pendingRequests, this.marketDataPrices, this.MAX_PRICE_HISTORY);
871
1539
  });
872
1540
  this.addWorkflows();
873
1541
  await this.server.connect(this.transport);
@@ -882,18 +1550,15 @@ var MCPLocal = class {
882
1550
  if (!this.server) {
883
1551
  return;
884
1552
  }
885
- this.server.setRequestHandler(
886
- z.object({ method: z.literal("tools/list") }),
887
- async (request, extra) => {
888
- return {
889
- tools: Object.entries(toolSchemas).map(([name, { description, schema }]) => ({
890
- name,
891
- description,
892
- inputSchema: schema
893
- }))
894
- };
895
- }
896
- );
1553
+ this.server.setRequestHandler(z.object({ method: z.literal("tools/list") }), async () => {
1554
+ return {
1555
+ tools: Object.entries(toolSchemas).map(([name, { description, schema }]) => ({
1556
+ name,
1557
+ description,
1558
+ inputSchema: schema
1559
+ }))
1560
+ };
1561
+ });
897
1562
  this.server.setRequestHandler(
898
1563
  z.object({
899
1564
  method: z.literal("tools/call"),
@@ -905,7 +1570,7 @@ var MCPLocal = class {
905
1570
  }).optional()
906
1571
  })
907
1572
  }),
908
- async (request, extra) => {
1573
+ async (request) => {
909
1574
  const { name, arguments: args } = request.params;
910
1575
  const toolHandlers = createToolHandlers(
911
1576
  this.parser,