UNPKG

fixparser-plugin-mcp

Version:

FIXParser MCP Plugin (Local/Remote)

4 lines 298 kB
{ "version": 3, "sources": ["../../src/RemoteServer.ts", "../../../fixparser-plugin-log-console/src/ConsoleLogTransport.ts", "../../src/mcp/MCPRemote.ts", "../../src/mcp/MCPBase.ts", "../../src/mcp/schemas/schemas.ts", "../../src/mcp/tools/indicators/momentum.ts", "../../src/mcp/tools/indicators/movingAverages.ts", "../../src/mcp/tools/indicators/options.ts", "../../src/mcp/tools/indicators/performance.ts", "../../src/mcp/tools/indicators/signals.ts", "../../src/mcp/tools/indicators/statistical.ts", "../../src/mcp/tools/indicators/supportResistance.ts", "../../src/mcp/tools/indicators/trend.ts", "../../src/mcp/tools/indicators/volatility.ts", "../../src/mcp/tools/indicators/volume.ts", "../../src/mcp/tools/analytics.ts", "../../src/mcp/tools/marketData.ts", "../../src/mcp/tools/order.ts", "../../src/mcp/tools/parse.ts", "../../src/mcp/tools/parseToJSON.ts", "../../src/mcp/tools/index.ts", "../../src/mcp/utils/messageHandler.ts"], "sourcesContent": ["import {\n EncryptMethod,\n FIXParser,\n Field,\n Fields,\n LicenseManager,\n Messages,\n type Options,\n ResetSeqNumFlag,\n} from 'fixparser';\nimport { ConsoleLogTransport } from 'fixparser-plugin-log-console';\nimport { MCPRemote } from './mcp/MCPRemote';\n\nconst initializeServer = async () => {\n await LicenseManager.setLicenseKey(process.env.FIXPARSER_LICENSE_KEY!);\n const SENDER = process.env.FIXPARSER_SENDER || 'SENDER';\n const TARGET = process.env.FIXPARSER_TARGET || 'TARGET';\n\n const fixParser: FIXParser = new FIXParser({\n plugins: [new MCPRemote({ port: 3099, onReady: () => console.log('ready!') })],\n });\n\n const sendLogon = () => {\n const logon = fixParser.createMessage(\n new Field(Fields.MsgType, Messages.Logon),\n new Field(Fields.MsgSeqNum, fixParser.getNextTargetMsgSeqNum()),\n new Field(Fields.SenderCompID, SENDER),\n new Field(Fields.SendingTime, fixParser.getTimestamp()),\n new Field(Fields.TargetCompID, TARGET),\n new Field(Fields.ResetSeqNumFlag, ResetSeqNumFlag.Yes),\n new Field(Fields.EncryptMethod, EncryptMethod.None),\n new Field(Fields.HeartBtInt, 10),\n );\n const messages = fixParser.parse(logon.encode());\n console.log('sending message', messages[0].description, messages[0].messageString);\n fixParser.send(logon);\n };\n\n const CONNECT_PARAMS: Options = {\n host: process.env.FIXPARSER_HOST || '10.0.1.42',\n port: process.env.FIXPARSER_PORT ? Number.parseInt(process.env.FIXPARSER_PORT, 10) : 5001,\n protocol: 'tcp',\n sender: SENDER,\n target: TARGET,\n fixVersion: 'FIX.4.4',\n logging: true,\n logOptions: {\n name: SENDER,\n level: 'info',\n format: 'json',\n transport: new ConsoleLogTransport({ format: 'console' }),\n },\n onOpen: () => {\n console.log('Open');\n sendLogon();\n },\n onClose: () => {\n fixParser.logger.log({\n level: 'info',\n message: 'FIXParser disconnected. Reconnecting in 1 second...',\n });\n setTimeout(() => {\n fixParser.connect(CONNECT_PARAMS);\n }, 1000);\n },\n };\n\n fixParser.connect(CONNECT_PARAMS);\n};\n\ninitializeServer().catch((err) => console.error('Error initializing server:', err));\n", "import type { ILogTransporter, LogMessage } from 'fixparser-common';\n\n/**\n * Logger output format options.\n *\n * - 'console': Output log in plain text format\n * - 'json': Output log in JSON format\n * - 'jsonrpc': Output log in JSON-RPC 2.0 format\n *\n * @public\n */\nexport type ConsoleFormat = 'console' | 'json' | 'jsonrpc';\n\n/**\n * A LogTransporter implementation for logging to the console.\n * It supports text (console), JSON, and JSON-RPC 2.0 formats.\n */\nexport class ConsoleLogTransport implements ILogTransporter {\n private format: ConsoleFormat;\n private useStderr: boolean;\n\n constructor({ format = 'json', useStderr = false }: { format: ConsoleFormat; useStderr?: boolean }) {\n this.format = format;\n this.useStderr = useStderr;\n }\n\n /**\n * Configures the format for console logging (either 'console' for text, 'json', or 'jsonrpc').\n */\n configure(config: { format: 'console' | 'json' | 'jsonrpc'; useStderr?: boolean }): void {\n this.format = config.format || 'json';\n if (config.useStderr !== undefined) {\n this.useStderr = config.useStderr;\n }\n }\n\n /**\n * Sends the log message to the console in the configured format.\n */\n async send(log: LogMessage): Promise<void> {\n const logMethod = this.useStderr ? console.error : console.log;\n\n if (this.format === 'json') {\n logMethod(JSON.stringify(log));\n } else if (this.format === 'jsonrpc') {\n const { message, ...rest } = log;\n const jsonrpcMessage = {\n jsonrpc: '2.0',\n method: log.level,\n params: {\n message,\n ...rest,\n },\n id: log.id || Date.now(),\n };\n logMethod(JSON.stringify(jsonrpcMessage));\n } else {\n const { name, id, message, level, ...additionalProperties } = log;\n const kv = Object.entries(additionalProperties).map(([key, value]) => `${key}: ${value}`);\n let logMessage = '';\n if (name) {\n logMessage += `${name} `;\n }\n logMessage += `${id}: ${message}`;\n void kv;\n void level;\n logMethod(logMessage, kv.join(', '));\n }\n }\n\n /**\n * Flushes the log buffer (if any buffering mechanism exists).\n */\n async flush(): Promise<void> {\n // No flushing needed for console transport\n }\n\n /**\n * Closes the transport (not needed for console, but keeping the method for consistency).\n */\n async close(): Promise<void> {\n // No close logic needed for console transport\n }\n\n /**\n * Returns the status of the transport (always \"connected\" for console).\n */\n status(): string {\n return 'connected';\n }\n}\n", "import { randomUUID } from 'node:crypto';\nimport type { Server } from 'node:http';\nimport { createServer } from 'node:http';\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';\nimport type { IFIXParser, Message } from 'fixparser';\nimport { z } from 'zod';\n\nimport { MCPBase } from './MCPBase';\nimport type { PluginOptions } from './PluginOptions';\nimport type { VerifiedOrder } from './schemas';\nimport type { MarketDataEntry } from './schemas/marketData';\nimport { toolSchemas } from './schemas/schemas';\nimport { createToolHandlers } from './tools';\nimport { handleMessage } from './utils/messageHandler';\n\nexport type RemotePluginOptions = PluginOptions & {\n port: number;\n};\n\nconst transports: Record<string, StreamableHTTPServerTransport> = {};\n\n// Helper function to convert JSON Schema to Zod schema\nfunction jsonSchemaToZod(schema: any): z.ZodRawShape {\n if (schema.type === 'object') {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const [key, prop] of Object.entries(schema.properties || {})) {\n const propSchema = prop as any;\n if (propSchema.type === 'string') {\n if (propSchema.enum) {\n shape[key] = z.enum(propSchema.enum as [string, ...string[]]);\n } else {\n shape[key] = z.string();\n }\n } else if (propSchema.type === 'number') {\n shape[key] = z.number();\n } else if (propSchema.type === 'boolean') {\n shape[key] = z.boolean();\n } else if (propSchema.type === 'array') {\n if (propSchema.items.type === 'string') {\n shape[key] = z.array(z.string());\n } else if (propSchema.items.type === 'number') {\n shape[key] = z.array(z.number());\n } else if (propSchema.items.type === 'boolean') {\n shape[key] = z.array(z.boolean());\n } else {\n shape[key] = z.array(z.any());\n }\n } else {\n shape[key] = z.any();\n }\n }\n return shape;\n }\n return {};\n}\n\nexport class MCPRemote extends MCPBase {\n /**\n * Port number the server will listen on.\n * @private\n */\n private port: number;\n\n /**\n * Node.js HTTP server instance created internally.\n * @private\n */\n private httpServer: Server | undefined;\n\n /**\n * MCP server instance handling MCP protocol logic.\n * @private\n */\n private mcpServer: McpServer | undefined;\n\n /**\n * Optional name of the plugin/server instance.\n * @private\n */\n private serverName: string | undefined;\n\n /**\n * Optional version string of the plugin/server.\n * @private\n */\n private serverVersion: string | undefined;\n\n /**\n * Map to store verified orders before execution\n * @private\n */\n protected override verifiedOrders: Map<string, VerifiedOrder> = new Map();\n\n /**\n * Map to store pending requests and their callbacks\n * @private\n */\n protected override pendingRequests: Map<string, (data: Message) => void> = new Map();\n\n /**\n * Map to store market data prices for each symbol\n * @private\n */\n protected override marketDataPrices: Map<string, MarketDataEntry[]> = new Map();\n\n /**\n * Maximum number of price history entries to keep per symbol\n * @private\n */\n protected override readonly MAX_PRICE_HISTORY = 100000;\n\n constructor({ port, logger, onReady }: RemotePluginOptions) {\n super({ logger, onReady });\n this.port = port;\n }\n\n public override async register(parser: IFIXParser): Promise<void> {\n this.parser = parser;\n this.logger = parser.logger;\n this.logger?.log({\n level: 'info',\n message: `FIXParser (MCP): -- Plugin registered. Creating MCP server on port ${this.port}...`,\n });\n\n // Add message callback handler\n this.parser.addOnMessageCallback((message: Message) => {\n if (this.parser) {\n handleMessage(\n message,\n this.parser,\n this.pendingRequests,\n this.marketDataPrices,\n this.MAX_PRICE_HISTORY,\n );\n }\n });\n\n this.httpServer = createServer(async (req, res) => {\n if (!req.url || !req.method) {\n res.writeHead(400);\n res.end('Bad Request');\n return;\n }\n\n if (req.url === '/mcp') {\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n\n if (req.method === 'POST') {\n const bodyChunks: Buffer[] = [];\n req.on('data', (chunk) => {\n bodyChunks.push(chunk);\n });\n req.on('end', async () => {\n let parsed: Record<string, any>;\n const body = Buffer.concat(bodyChunks).toString();\n try {\n parsed = JSON.parse(body);\n } catch (error) {\n void error;\n res.writeHead(400);\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n return;\n }\n\n let transport: StreamableHTTPServerTransport;\n\n if (sessionId && transports[sessionId]) {\n transport = transports[sessionId];\n } else if (!sessionId && req.method === 'POST' && isInitializeRequest(parsed)) {\n transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n onsessioninitialized: (sessionId) => {\n transports[sessionId] = transport;\n },\n });\n\n transport.onclose = () => {\n if (transport.sessionId) {\n delete transports[transport.sessionId];\n }\n };\n\n this.mcpServer = new McpServer({\n name: this.serverName || 'FIXParser',\n version: this.serverVersion || '1.0.0',\n });\n\n this.setupTools();\n\n await this.mcpServer.connect(transport);\n } else {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32000,\n message: 'Bad Request: No valid session ID provided',\n },\n id: null,\n }),\n );\n return;\n }\n\n try {\n await transport.handleRequest(req, res, parsed);\n } catch (error) {\n this.logger?.log({\n level: 'error',\n message: `Error handling request: ${error}`,\n });\n throw error;\n }\n });\n } else if (req.method === 'GET' || req.method === 'DELETE') {\n if (!sessionId || !transports[sessionId]) {\n res.writeHead(400);\n res.end('Invalid or missing session ID');\n return;\n }\n const transport = transports[sessionId];\n try {\n await transport.handleRequest(req, res);\n } catch (error) {\n this.logger?.log({\n level: 'error',\n message: `Error handling ${req.method} request: ${error}`,\n });\n throw error;\n }\n } else {\n this.logger?.log({\n level: 'error',\n message: `Method not allowed: ${req.method}`,\n });\n res.writeHead(405);\n res.end('Method Not Allowed');\n }\n } else {\n res.writeHead(404);\n res.end('Not Found');\n }\n });\n\n this.httpServer.listen(this.port, () => {\n this.logger?.log({\n level: 'info',\n message: `FIXParser (MCP): -- Server listening on http://localhost:${this.port}...`,\n });\n });\n\n if (this.onReady) {\n this.onReady();\n }\n }\n\n private setupTools(): void {\n if (!this.parser) {\n this.logger?.log({\n level: 'error',\n message: 'FIXParser (MCP): -- FIXParser instance not initialized. Ignoring setup of tools...',\n });\n return;\n }\n\n if (!this.mcpServer) {\n this.logger?.log({\n level: 'error',\n message: 'FIXParser (MCP): -- MCP Server not initialized. Ignoring setup of tools...',\n });\n return;\n }\n\n // Create tool handlers\n const toolHandlers = createToolHandlers(\n this.parser,\n this.verifiedOrders,\n this.pendingRequests,\n this.marketDataPrices,\n );\n\n // Register each tool with its schema\n Object.entries(toolSchemas).forEach(([name, { description, schema }]) => {\n this.mcpServer?.registerTool(\n name,\n {\n description,\n inputSchema: jsonSchemaToZod(schema),\n },\n async (args) => {\n const handler = toolHandlers[name];\n if (!handler) {\n return {\n content: [\n {\n type: 'text',\n text: `Tool not found: ${name}`,\n },\n ],\n isError: true,\n };\n }\n\n const result = await handler(args);\n return {\n content: result.content,\n isError: result.isError,\n };\n },\n );\n });\n }\n}\n", "import type { IFIXParser, Logger } from 'fixparser';\nimport type { IPlugin } from 'fixparser-common';\n\nimport type { VerifiedOrder } from './schemas';\nimport type { MarketDataPrices, PendingRequests } from './utils/messageHandler';\n\nexport abstract class MCPBase implements IPlugin<IFIXParser> {\n /**\n * Optional logger instance for diagnostics and output.\n * @protected\n */\n protected logger: Logger | undefined;\n\n /**\n * FIXParser instance, set during plugin register().\n * @protected\n */\n protected parser: IFIXParser | undefined;\n\n /**\n * Called when server is setup and listening.\n * @protected\n */\n protected onReady: (() => void) | undefined = undefined;\n\n /**\n * Map to store verified orders before execution\n * @protected\n */\n protected verifiedOrders: Map<string, VerifiedOrder> = new Map();\n\n /**\n * Map to store pending market data requests\n * @protected\n */\n protected pendingRequests: PendingRequests = new Map();\n\n /**\n * Map to store market data prices\n * @protected\n */\n protected marketDataPrices: MarketDataPrices = new Map();\n\n /**\n * Maximum number of price history entries to keep per symbol\n * @protected\n */\n protected readonly MAX_PRICE_HISTORY = 100000;\n\n constructor({ logger, onReady }: { logger?: Logger; onReady?: () => void }) {\n this.logger = logger;\n this.onReady = onReady;\n }\n\n public abstract register(parser: IFIXParser): Promise<void>;\n}\n", "export const toolSchemas = {\n parse: {\n description: 'Parses a FIX message and describes it in plain language',\n schema: {\n type: 'object',\n properties: {\n fixString: { type: 'string' },\n },\n required: ['fixString'],\n },\n },\n parseToJSON: {\n description: 'Parses a FIX message into JSON',\n schema: {\n type: 'object',\n properties: {\n fixString: { type: 'string' },\n },\n required: ['fixString'],\n },\n },\n verifyOrder: {\n description: 'Verifies order parameters before execution. verifyOrder must be called before executeOrder.',\n schema: {\n type: 'object',\n properties: {\n clOrdID: { type: 'string' },\n handlInst: {\n type: 'string',\n enum: ['1', '2', '3'],\n description:\n 'Handling Instructions: 1=Automated Execution No Intervention, 2=Automated Execution Intervention OK, 3=Manual Order',\n },\n quantity: { type: 'string' },\n price: { type: 'string' },\n ordType: {\n type: 'string',\n enum: [\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n 'A',\n 'B',\n 'C',\n 'D',\n 'E',\n 'F',\n 'G',\n 'H',\n 'I',\n 'J',\n 'K',\n 'L',\n 'M',\n 'P',\n 'Q',\n 'R',\n 'S',\n ],\n description:\n 'Order Type: 1=Market, 2=Limit, 3=Stop, 4=StopLimit, 5=MarketOnClose, 6=WithOrWithout, 7=LimitOrBetter, 8=LimitWithOrWithout, 9=OnBasis, A=OnClose, B=LimitOnClose, C=ForexMarket, D=PreviouslyQuoted, E=PreviouslyIndicated, F=ForexLimit, G=ForexSwap, H=ForexPreviouslyQuoted, I=Funari, J=MarketIfTouched, K=MarketWithLeftOverAsLimit, L=PreviousFundValuationPoint, M=NextFundValuationPoint, P=Pegged, Q=CounterOrderSelection, R=StopOnBidOrOffer, S=StopLimitOnBidOrOffer',\n },\n side: {\n type: 'string',\n enum: ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],\n description:\n 'Side: 1=Buy, 2=Sell, 3=BuyMinus, 4=SellPlus, 5=SellShort, 6=SellShortExempt, 7=Undisclosed, 8=Cross, 9=CrossShort, A=CrossShortExempt, B=AsDefined, C=Opposite, D=Subscribe, E=Redeem, F=Lend, G=Borrow, H=SellUndisclosed',\n },\n symbol: { type: 'string' },\n timeInForce: {\n type: 'string',\n enum: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'],\n description:\n 'Time In Force: 0=Day, 1=GoodTillCancel, 2=AtTheOpening, 3=ImmediateOrCancel, 4=FillOrKill, 5=GoodTillCrossing, 6=GoodTillDate, 7=AtTheClose, 8=GoodThroughCrossing, 9=AtCrossing, A=GoodForTime, B=GoodForAuction, C=GoodForMonth',\n },\n },\n required: ['clOrdID', 'handlInst', 'quantity', 'price', 'ordType', 'side', 'symbol', 'timeInForce'],\n },\n },\n executeOrder: {\n description: 'Executes a verified order. verifyOrder must be called before executeOrder.',\n schema: {\n type: 'object',\n properties: {\n clOrdID: { type: 'string' },\n handlInst: {\n type: 'string',\n enum: ['1', '2', '3'],\n description:\n 'Handling Instructions: 1=Automated Execution No Intervention, 2=Automated Execution Intervention OK, 3=Manual Order',\n },\n quantity: { type: 'string' },\n price: { type: 'string' },\n ordType: {\n type: 'string',\n enum: [\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n 'A',\n 'B',\n 'C',\n 'D',\n 'E',\n 'F',\n 'G',\n 'H',\n 'I',\n 'J',\n 'K',\n 'L',\n 'M',\n 'P',\n 'Q',\n 'R',\n 'S',\n ],\n description:\n 'Order Type: 1=Market, 2=Limit, 3=Stop, 4=StopLimit, 5=MarketOnClose, 6=WithOrWithout, 7=LimitOrBetter, 8=LimitWithOrWithout, 9=OnBasis, A=OnClose, B=LimitOnClose, C=ForexMarket, D=PreviouslyQuoted, E=PreviouslyIndicated, F=ForexLimit, G=ForexSwap, H=ForexPreviouslyQuoted, I=Funari, J=MarketIfTouched, K=MarketWithLeftOverAsLimit, L=PreviousFundValuationPoint, M=NextFundValuationPoint, P=Pegged, Q=CounterOrderSelection, R=StopOnBidOrOffer, S=StopLimitOnBidOrOffer',\n },\n side: {\n type: 'string',\n enum: ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'],\n description:\n 'Side: 1=Buy, 2=Sell, 3=BuyMinus, 4=SellPlus, 5=SellShort, 6=SellShortExempt, 7=Undisclosed, 8=Cross, 9=CrossShort, A=CrossShortExempt, B=AsDefined, C=Opposite, D=Subscribe, E=Redeem, F=Lend, G=Borrow, H=SellUndisclosed',\n },\n symbol: { type: 'string' },\n timeInForce: {\n type: 'string',\n enum: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'],\n description:\n 'Time In Force: 0=Day, 1=GoodTillCancel, 2=AtTheOpening, 3=ImmediateOrCancel, 4=FillOrKill, 5=GoodTillCrossing, 6=GoodTillDate, 7=AtTheClose, 8=GoodThroughCrossing, 9=AtCrossing, A=GoodForTime, B=GoodForAuction, C=GoodForMonth',\n },\n },\n required: ['clOrdID', 'handlInst', 'quantity', 'price', 'ordType', 'side', 'symbol', 'timeInForce'],\n },\n },\n marketDataRequest: {\n description: 'Requests market data for specified symbols',\n schema: {\n type: 'object',\n properties: {\n mdUpdateType: {\n type: 'string',\n enum: ['0', '1'],\n description: 'Market Data Update Type: 0=Full Refresh, 1=Incremental Refresh',\n },\n symbols: { type: 'array', items: { type: 'string' } },\n mdReqID: { type: 'string' },\n subscriptionRequestType: {\n type: 'string',\n enum: ['0', '1', '2'],\n description:\n 'Subscription Request Type: 0=Snapshot, 1=Snapshot + Updates, 2=Disable Previous Snapshot + Update Request',\n },\n mdEntryTypes: {\n type: 'array',\n items: {\n type: 'string',\n enum: [\n '0',\n '1',\n '2',\n '3',\n '4',\n '5',\n '6',\n '7',\n '8',\n '9',\n 'A',\n 'B',\n 'C',\n 'D',\n 'E',\n 'F',\n 'G',\n 'H',\n 'I',\n 'J',\n 'K',\n 'L',\n 'M',\n 'N',\n 'O',\n 'P',\n 'Q',\n 'R',\n 'S',\n 'T',\n 'U',\n 'V',\n 'W',\n 'X',\n 'Y',\n 'Z',\n ],\n },\n description:\n 'Market Data Entry Types: 0=Bid, 1=Offer, 2=Trade, 3=Index Value, 4=Opening Price, 5=Closing Price, 6=Settlement Price, 7=High Price, 8=Low Price, 9=Trade Volume, A=Open Interest, B=Simulated Sell Price, C=Simulated Buy Price, D=Empty Book, E=Session High Bid, F=Session Low Offer, G=Fixing Price, H=Electronic Volume, I=Threshold Limits and Price Band Variation, J=Clearing Price, K=Open Interest Change, L=Last Trade Price, M=Last Trade Volume, N=Last Trade Time, O=Last Trade Tick, P=Last Trade Exchange, Q=Last Trade ID, R=Last Trade Side, S=Last Trade Price Change, T=Last Trade Price Change Percent, U=Last Trade Price Change Basis Points, V=Last Trade Price Change Points, W=Last Trade Price Change Ticks, X=Last Trade Price Change Ticks Percent, Y=Last Trade Price Change Ticks Basis Points, Z=Last Trade Price Change Ticks Points',\n },\n },\n required: ['mdUpdateType', 'symbols', 'mdReqID', 'subscriptionRequestType'],\n },\n },\n getStockGraph: {\n description: 'Generates a price chart for a given symbol',\n schema: {\n type: 'object',\n properties: {\n symbol: { type: 'string' },\n },\n required: ['symbol'],\n },\n },\n getStockPriceHistory: {\n description: 'Returns price history for a given symbol',\n schema: {\n type: 'object',\n properties: {\n symbol: { type: 'string' },\n },\n required: ['symbol'],\n },\n },\n technicalAnalysis: {\n description:\n 'Performs comprehensive technical analysis on market data for a given symbol, including indicators like SMA, EMA, RSI, Bollinger Bands, and trading signals',\n schema: {\n type: 'object',\n properties: {\n symbol: {\n type: 'string',\n description: 'The trading symbol to analyze (e.g., AAPL, MSFT, EURUSD)',\n },\n },\n required: ['symbol'],\n },\n },\n};\n", "import type { Stochastic } from '../../schemas/indicatortypes.ts';\n\n// Momentum Indicators\nexport class MomentumIndicators {\n /**\n * Calculate RSI (Relative Strength Index)\n */\n static calculateRSI(data: number[], period = 14): number[] {\n if (data.length < period + 1) return [];\n\n const changes: number[] = [];\n for (let i = 1; i < data.length; i++) {\n changes.push(data[i] - data[i - 1]);\n }\n\n const gains = changes.map((change) => (change > 0 ? change : 0));\n const losses = changes.map((change) => (change < 0 ? Math.abs(change) : 0));\n\n let avgGain = gains.slice(0, period).reduce((a: number, b: number) => a + b, 0) / period;\n let avgLoss = losses.slice(0, period).reduce((a: number, b: number) => a + b, 0) / period;\n\n const rsi: number[] = [];\n\n for (let i = period; i < changes.length; i++) {\n const rs = avgGain / avgLoss;\n rsi.push(100 - 100 / (1 + rs));\n\n avgGain = (avgGain * (period - 1) + gains[i]) / period;\n avgLoss = (avgLoss * (period - 1) + losses[i]) / period;\n }\n\n return rsi;\n }\n\n /**\n * Calculate Stochastic Oscillator\n */\n static calculateStochastic(prices: number[], highs: number[], lows: number[]): Stochastic[] {\n const stochastic: Stochastic[] = [];\n const period = 14;\n const smoothK = 3;\n const smoothD = 3;\n\n if (prices.length < period) return [];\n\n // Calculate %K\n const percentK: number[] = [];\n for (let i = period - 1; i < prices.length; i++) {\n const high = Math.max(...highs.slice(i - period + 1, i + 1));\n const low = Math.min(...lows.slice(i - period + 1, i + 1));\n const close = prices[i];\n\n const k = ((close - low) / (high - low)) * 100;\n percentK.push(k);\n }\n\n // Smooth %K\n const smoothedK: number[] = [];\n for (let i = smoothK - 1; i < percentK.length; i++) {\n const sum = percentK.slice(i - smoothK + 1, i + 1).reduce((a, b) => a + b, 0);\n smoothedK.push(sum / smoothK);\n }\n\n // Calculate %D (smooth of %K)\n for (let i = smoothD - 1; i < smoothedK.length; i++) {\n const sum = smoothedK.slice(i - smoothD + 1, i + 1).reduce((a, b) => a + b, 0);\n const d = sum / smoothD;\n\n stochastic.push({\n k: smoothedK[i],\n d: d,\n });\n }\n\n return stochastic;\n }\n\n /**\n * Calculate CCI (Commodity Channel Index)\n */\n static calculateCCI(prices: number[], highs: number[], lows: number[]): number[] {\n const cci: number[] = [];\n const period = 20;\n\n if (prices.length < period) return [];\n\n for (let i = period - 1; i < prices.length; i++) {\n const slice = prices.slice(i - period + 1, i + 1);\n const typicalPrices = slice.map((price, idx) => {\n const high = highs[i - period + 1 + idx] || price;\n const low = lows[i - period + 1 + idx] || price;\n return (high + low + price) / 3;\n });\n\n const sma = typicalPrices.reduce((a, b) => a + b, 0) / period;\n const meanDeviation = typicalPrices.reduce((sum, tp) => sum + Math.abs(tp - sma), 0) / period;\n\n const currentTP = (highs[i] + lows[i] + prices[i]) / 3;\n const cciValue = meanDeviation !== 0 ? (currentTP - sma) / (0.015 * meanDeviation) : 0;\n\n cci.push(cciValue);\n }\n\n return cci;\n }\n\n /**\n * Calculate Rate of Change\n */\n static calculateROC(prices: number[]): number[] {\n const roc: number[] = [];\n for (let i = 10; i < prices.length; i++) {\n roc.push(((prices[i] - prices[i - 10]) / prices[i - 10]) * 100);\n }\n return roc;\n }\n\n /**\n * Calculate Williams %R\n */\n static calculateWilliamsR(prices: number[]): number[] {\n const williamsR: number[] = [];\n const period = 14;\n\n if (prices.length < period) return [];\n\n for (let i = period - 1; i < prices.length; i++) {\n const slice = prices.slice(i - period + 1, i + 1);\n const high = Math.max(...slice);\n const low = Math.min(...slice);\n const close = prices[i];\n\n const wr = ((high - close) / (high - low)) * -100;\n williamsR.push(wr);\n }\n\n return williamsR;\n }\n\n /**\n * Calculate Momentum\n */\n static calculateMomentum(prices: number[]): number[] {\n const momentum: number[] = [];\n for (let i = 10; i < prices.length; i++) {\n momentum.push(prices[i] - prices[i - 10]);\n }\n return momentum;\n }\n}\n", "// Moving Average Indicators\nexport class MovingAverages {\n /**\n * Calculate Simple Moving Average\n */\n static calculateSMA(data: number[], period: number): number[] {\n const sma: number[] = [];\n for (let i = period - 1; i < data.length; i++) {\n const sum = data.slice(i - period + 1, i + 1).reduce((a: number, b: number) => a + b, 0);\n sma.push(sum / period);\n }\n return sma;\n }\n\n /**\n * Calculate Exponential Moving Average\n */\n static calculateEMA(data: number[], period: number): number[] {\n const multiplier = 2 / (period + 1);\n const ema: number[] = [data[0]];\n\n for (let i = 1; i < data.length; i++) {\n ema.push(data[i] * multiplier + ema[i - 1] * (1 - multiplier));\n }\n return ema;\n }\n\n /**\n * Calculate Weighted Moving Average\n */\n static calculateWMA(data: number[], period: number): number[] {\n const wma: number[] = [];\n const weights = Array.from({ length: period }, (_, i) => i + 1);\n const weightSum = weights.reduce((a, b) => a + b, 0);\n\n for (let i = period - 1; i < data.length; i++) {\n let weightedSum = 0;\n for (let j = 0; j < period; j++) {\n weightedSum += data[i - j] * weights[j];\n }\n wma.push(weightedSum / weightSum);\n }\n\n return wma;\n }\n\n /**\n * Calculate Volume Weighted Moving Average\n */\n static calculateVWMA(prices: number[], volumes: number[], period: number): number[] {\n const vwma: number[] = [];\n\n for (let i = period - 1; i < prices.length; i++) {\n let volumeSum = 0;\n let priceVolumeSum = 0;\n\n for (let j = 0; j < period; j++) {\n const volume = volumes[i - j] || 1;\n volumeSum += volume;\n priceVolumeSum += prices[i - j] * volume;\n }\n\n vwma.push(priceVolumeSum / volumeSum);\n }\n\n return vwma;\n }\n}\n", "import type { BlackScholes } from '../../schemas/indicatortypes.ts';\n\n// Options Analysis\nexport class OptionsAnalysis {\n /**\n * Calculate Black-Scholes Option Pricing\n */\n static calculateBlackScholes(currentPrice: number, startPrice: number, avgVolume: number): BlackScholes | null {\n const S = currentPrice;\n const K = startPrice;\n const T = 1; // 1 year\n const r = 0.05; // 5% risk-free rate\n const sigma = avgVolume * 0.01; // Volatility\n\n const d1 = (Math.log(S / K) + (r + (sigma * sigma) / 2) * T) / (sigma * Math.sqrt(T));\n const d2 = d1 - sigma * Math.sqrt(T);\n\n const callPrice = S * OptionsAnalysis.normalCDF(d1) - K * Math.exp(-r * T) * OptionsAnalysis.normalCDF(d2);\n const putPrice = K * Math.exp(-r * T) * OptionsAnalysis.normalCDF(-d2) - S * OptionsAnalysis.normalCDF(-d1);\n\n return {\n callPrice,\n putPrice,\n delta: OptionsAnalysis.normalCDF(d1),\n gamma: OptionsAnalysis.normalPDF(d1) / (S * sigma * Math.sqrt(T)),\n theta:\n (-S * OptionsAnalysis.normalPDF(d1) * sigma) / (2 * Math.sqrt(T)) -\n r * K * Math.exp(-r * T) * OptionsAnalysis.normalCDF(d2),\n vega: S * Math.sqrt(T) * OptionsAnalysis.normalPDF(d1),\n rho: K * T * Math.exp(-r * T) * OptionsAnalysis.normalCDF(d2),\n };\n }\n\n /**\n * Calculate Binomial Tree Option Pricing\n */\n static calculateBinomialTree(\n currentPrice: number,\n strikePrice: number,\n timeToExpiry = 1,\n riskFreeRate = 0.05,\n volatility = 0.2,\n steps = 100,\n ): {\n callPrice: number;\n putPrice: number;\n delta: number;\n gamma: number;\n theta: number;\n vega: number;\n rho: number;\n } {\n const dt = timeToExpiry / steps;\n const u = Math.exp(volatility * Math.sqrt(dt));\n const d = 1 / u;\n const p = (Math.exp(riskFreeRate * dt) - d) / (u - d);\n\n // Build price tree\n const priceTree: number[][] = [];\n for (let i = 0; i <= steps; i++) {\n priceTree[i] = [];\n for (let j = 0; j <= i; j++) {\n priceTree[i][j] = currentPrice * u ** j * d ** (i - j);\n }\n }\n\n // Calculate call option values at expiration\n const callTree: number[][] = [];\n callTree[steps] = [];\n for (let j = 0; j <= steps; j++) {\n const callValue = Math.max(0, priceTree[steps][j] - strikePrice);\n callTree[steps][j] = callValue;\n }\n\n // Calculate put option values at expiration\n const putTree: number[][] = [];\n putTree[steps] = [];\n for (let j = 0; j <= steps; j++) {\n const putValue = Math.max(0, strikePrice - priceTree[steps][j]);\n putTree[steps][j] = putValue;\n }\n\n // Backward induction for call options\n for (let i = steps - 1; i >= 0; i--) {\n callTree[i] = [];\n for (let j = 0; j <= i; j++) {\n const upValue = callTree[i + 1][j + 1];\n const downValue = callTree[i + 1][j];\n const optionValue = Math.exp(-riskFreeRate * dt) * (p * upValue + (1 - p) * downValue);\n callTree[i][j] = Math.max(optionValue, priceTree[i][j] - strikePrice); // American style\n }\n }\n\n // Backward induction for put options\n for (let i = steps - 1; i >= 0; i--) {\n putTree[i] = [];\n for (let j = 0; j <= i; j++) {\n const upValue = putTree[i + 1][j + 1];\n const downValue = putTree[i + 1][j];\n const optionValue = Math.exp(-riskFreeRate * dt) * (p * upValue + (1 - p) * downValue);\n putTree[i][j] = Math.max(optionValue, strikePrice - priceTree[i][j]); // American style\n }\n }\n\n const callPrice = callTree[0][0];\n const putPrice = putTree[0][0];\n\n // Calculate Greeks (approximations)\n const delta = (callTree[1][1] - callTree[1][0]) / (priceTree[1][1] - priceTree[1][0]);\n const gamma =\n ((callTree[1][1] - callTree[1][0]) / (priceTree[1][1] - priceTree[1][0]) -\n (callTree[1][0] - callTree[1][0]) / (priceTree[1][0] - priceTree[1][0])) /\n (priceTree[1][1] - priceTree[1][0]);\n const theta = (callTree[1][0] - callTree[0][0]) / dt;\n const vega = (callPrice - callPrice * 1.01) / (volatility * 0.01);\n const rho = (callPrice - callPrice * 1.01) / (riskFreeRate * 0.01);\n\n return {\n callPrice,\n putPrice,\n delta,\n gamma,\n theta,\n vega,\n rho,\n };\n }\n\n /**\n * Calculate Trinomial Tree Option Pricing\n */\n static calculateTrinomialTree(\n currentPrice: number,\n strikePrice: number,\n timeToExpiry = 1,\n riskFreeRate = 0.05,\n volatility = 0.2,\n steps = 50,\n ): {\n callPrice: number;\n putPrice: number;\n delta: number;\n gamma: number;\n theta: number;\n vega: number;\n rho: number;\n } {\n const dt = timeToExpiry / steps;\n const dx = volatility * Math.sqrt(3 * dt);\n const u = Math.exp(dx);\n\n // Risk-neutral probabilities\n const pu =\n 0.5 *\n (((riskFreeRate - 0.5 * volatility * volatility) * dt) / dx + (volatility * volatility * dt) / (dx * dx));\n const pd =\n 0.5 *\n (((riskFreeRate - 0.5 * volatility * volatility) * dt) / dx - (volatility * volatility * dt) / (dx * dx));\n const pm = 1 - pu - pd;\n\n // Build price tree\n const priceTree: number[][] = [];\n for (let i = 0; i <= steps; i++) {\n priceTree[i] = [];\n for (let j = 0; j <= 2 * i + 1; j++) {\n const priceChange = j - i;\n priceTree[i][j] = currentPrice * u ** priceChange;\n }\n }\n\n // Calculate call option values at expiration\n const callTree: number[][] = [];\n callTree[steps] = [];\n for (let j = 0; j <= 2 * steps; j++) {\n const callValue = Math.max(0, priceTree[steps][j] - strikePrice);\n callTree[steps][j] = callValue;\n }\n\n // Calculate put option values at expiration\n const putTree: number[][] = [];\n putTree[steps] = [];\n for (let j = 0; j <= 2 * steps; j++) {\n const putValue = Math.max(0, strikePrice - priceTree[steps][j]);\n putTree[steps][j] = putValue;\n }\n\n // Backward induction for call options\n for (let i = steps - 1; i >= 0; i--) {\n callTree[i] = [];\n for (let j = 0; j <= 2 * i; j++) {\n const upValue = callTree[i + 1][j + 2];\n const midValue = callTree[i + 1][j + 1];\n const downValue = callTree[i + 1][j];\n const optionValue = Math.exp(-riskFreeRate * dt) * (pu * upValue + pm * midValue + pd * downValue);\n callTree[i][j] = Math.max(optionValue, priceTree[i][j] - strikePrice);\n }\n }\n\n // Backward induction for put options\n for (let i = steps - 1; i >= 0; i--) {\n putTree[i] = [];\n for (let j = 0; j <= 2 * i; j++) {\n const upValue = putTree[i + 1][j + 2];\n const midValue = putTree[i + 1][j + 1];\n const downValue = putTree[i + 1][j];\n const optionValue = Math.exp(-riskFreeRate * dt) * (pu * upValue + pm * midValue + pd * downValue);\n putTree[i][j] = Math.max(optionValue, strikePrice - priceTree[i][j]);\n }\n }\n\n const callPrice = callTree[0][0];\n const putPrice = putTree[0][0];\n\n // Calculate Greeks\n const delta = (callTree[1][2] - callTree[1][0]) / (priceTree[1][2] - priceTree[1][0]);\n const gamma =\n ((callTree[1][2] - callTree[1][1]) / (priceTree[1][2] - priceTree[1][1]) -\n (callTree[1][1] - callTree[1][0]) / (priceTree[1][1] - priceTree[1][0])) /\n (priceTree[1][2] - priceTree[1][0]);\n const theta = (callTree[1][1] - callTree[0][0]) / dt;\n const vega = (callPrice - callPrice * 1.01) / (volatility * 0.01);\n const rho = (callPrice - callPrice * 1.01) / (riskFreeRate * 0.01);\n\n return {\n callPrice,\n putPrice,\n delta,\n gamma,\n theta,\n vega,\n rho,\n };\n }\n\n /**\n * Calculate Monte Carlo Option Pricing\n */\n static calculateMonteCarlo(\n currentPrice: number,\n strikePrice: number,\n timeToExpiry = 1,\n riskFreeRate = 0.05,\n volatility = 0.2,\n simulations = 10000,\n ): {\n callPrice: number;\n putPrice: number;\n delta: number;\n gamma: number;\n theta: number;\n vega: number;\n rho: number;\n confidenceInterval: { lower: number; upper: number };\n } {\n let callSum = 0;\n let putSum = 0;\n let deltaSum = 0;\n let gammaSum = 0;\n\n for (let i = 0; i < simulations; i++) {\n // Generate random walk\n const z = OptionsAnalysis.boxMuller();\n const finalPrice =\n currentPrice *\n Math.exp(\n (riskFreeRate - 0.5 * volatility * volatility) * timeToExpiry +\n volatility * Math.sqrt(timeToExpiry) * z,\n );\n\n // Calculate payoffs\n const callPayoff = Math.max(0, finalPrice - strikePrice);\n const putPayoff = Math.max(0, strikePrice - finalPrice);\n\n callSum += callPayoff;\n putSum += putPayoff;\n\n // Calculate Greeks (approximations)\n if (finalPrice > strikePrice) {\n deltaSum += 1;\n gammaSum += 0;\n }\n }\n\n const discountFactor = Math.exp(-riskFreeRate * timeToExpiry);\n const callPrice = (callSum / simulations) * discountFactor;\n const putPrice = (putSum / simulations) * discountFactor;\n\n // Calculate Greeks\n const delta = (deltaSum / simulations) * discountFactor;\n const gamma = (gammaSum / simulations) * discountFactor;\n const theta = -(callPrice * riskFreeRate);\n const vega = (callPrice - callPrice * 1.01) / (volatility * 0.01);\n const rho = (callPrice - callPrice * 1.01) / (riskFreeRate * 0.01);\n\n // Calculate confidence interval\n const stdError = Math.sqrt((callPrice * (1 - callPrice)) / simulations);\n const confidenceInterval = {\n lower: callPrice - 1.96 * stdError,\n upper: callPrice + 1.96 * stdError,\n };\n\n return {\n callPrice,\n putPrice,\n delta,\n gamma,\n theta,\n vega,\n rho,\n confidenceInterval,\n };\n }\n\n /**\n * Calculate Heston Model (Stochastic Volatility)\n */\n static calculateHestonModel(\n currentPrice: number,\n strikePrice: number,\n timeToExpiry = 1,\n riskFreeRate = 0.05,\n _initialVolatility = 0.2,\n _longTermVolatility = 0.2,\n