@claude-flow/plugin-financial-risk 3.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -0
- package/dist/bridges/economy-bridge.d.ts +112 -0
- package/dist/bridges/economy-bridge.d.ts.map +1 -0
- package/dist/bridges/economy-bridge.js +430 -0
- package/dist/bridges/economy-bridge.js.map +1 -0
- package/dist/bridges/index.d.ts +8 -0
- package/dist/bridges/index.d.ts.map +1 -0
- package/dist/bridges/index.js +8 -0
- package/dist/bridges/index.js.map +1 -0
- package/dist/bridges/sparse-bridge.d.ts +118 -0
- package/dist/bridges/sparse-bridge.d.ts.map +1 -0
- package/dist/bridges/sparse-bridge.js +450 -0
- package/dist/bridges/sparse-bridge.js.map +1 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +155 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-tools.d.ts +22 -0
- package/dist/mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools.js +705 -0
- package/dist/mcp-tools.js.map +1 -0
- package/dist/types.d.ts +792 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +189 -0
- package/dist/types.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Financial Risk MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* High-performance financial risk analysis tools including:
|
|
5
|
+
* - portfolio-risk: Calculate VaR, CVaR, Sharpe, and other risk metrics
|
|
6
|
+
* - anomaly-detect: Detect anomalies in transactions using GNN
|
|
7
|
+
* - market-regime: Classify current market regime using pattern matching
|
|
8
|
+
* - compliance-check: Verify regulatory compliance (Basel III, MiFID II, etc.)
|
|
9
|
+
* - stress-test: Run stress testing scenarios on portfolios
|
|
10
|
+
*/
|
|
11
|
+
import { PortfolioRiskInputSchema, AnomalyDetectInputSchema, MarketRegimeInputSchema, ComplianceCheckInputSchema, StressTestInputSchema, successResult, errorResult, FinancialRolePermissions, FinancialRateLimits, FinancialErrorCodes, } from './types.js';
|
|
12
|
+
import { FinancialEconomyBridge } from './bridges/economy-bridge.js';
|
|
13
|
+
import { FinancialSparseBridge } from './bridges/sparse-bridge.js';
|
|
14
|
+
// Default logger
|
|
15
|
+
const defaultLogger = {
|
|
16
|
+
debug: (msg, meta) => console.debug(`[financial-tools] ${msg}`, meta),
|
|
17
|
+
info: (msg, meta) => console.info(`[financial-tools] ${msg}`, meta),
|
|
18
|
+
warn: (msg, meta) => console.warn(`[financial-tools] ${msg}`, meta),
|
|
19
|
+
error: (msg, meta) => console.error(`[financial-tools] ${msg}`, meta),
|
|
20
|
+
};
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Authorization & Rate Limiting
|
|
23
|
+
// ============================================================================
|
|
24
|
+
function checkAuthorization(toolName, context) {
|
|
25
|
+
if (!context?.userRoles)
|
|
26
|
+
return true;
|
|
27
|
+
for (const role of context.userRoles) {
|
|
28
|
+
const permissions = FinancialRolePermissions[role];
|
|
29
|
+
if (permissions?.includes(toolName))
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// Simple in-memory rate limiter
|
|
35
|
+
const rateLimitState = new Map();
|
|
36
|
+
function checkRateLimit(toolName, userId) {
|
|
37
|
+
const key = `${toolName}:${userId}`;
|
|
38
|
+
const limit = FinancialRateLimits[toolName];
|
|
39
|
+
if (!limit)
|
|
40
|
+
return true;
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
const state = rateLimitState.get(key);
|
|
43
|
+
if (!state || state.resetAt < now) {
|
|
44
|
+
rateLimitState.set(key, { count: 1, resetAt: now + 60000 });
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (state.count >= limit.requestsPerMinute) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
state.count++;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
async function logAudit(toolName, context, input, output, durationMs) {
|
|
54
|
+
if (!context?.auditLogger)
|
|
55
|
+
return;
|
|
56
|
+
const entry = {
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
userId: context.userId ?? 'anonymous',
|
|
59
|
+
toolName,
|
|
60
|
+
transactionIds: [],
|
|
61
|
+
portfolioHash: hashObject(input),
|
|
62
|
+
riskMetricsComputed: [],
|
|
63
|
+
modelVersion: '1.0.0',
|
|
64
|
+
inputHash: hashObject(input),
|
|
65
|
+
outputHash: hashObject(output),
|
|
66
|
+
executionTimeMs: durationMs,
|
|
67
|
+
regulatoryFlags: [],
|
|
68
|
+
};
|
|
69
|
+
await context.auditLogger.log(entry);
|
|
70
|
+
}
|
|
71
|
+
function hashObject(obj) {
|
|
72
|
+
const str = JSON.stringify(obj);
|
|
73
|
+
let hash = 0;
|
|
74
|
+
for (let i = 0; i < str.length; i++) {
|
|
75
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
76
|
+
hash = hash & hash;
|
|
77
|
+
}
|
|
78
|
+
return Math.abs(hash).toString(16).padStart(8, '0');
|
|
79
|
+
}
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// Portfolio Risk Tool
|
|
82
|
+
// ============================================================================
|
|
83
|
+
async function portfolioRiskHandler(input, context) {
|
|
84
|
+
const logger = context?.logger ?? defaultLogger;
|
|
85
|
+
const startTime = performance.now();
|
|
86
|
+
try {
|
|
87
|
+
// Authorization check
|
|
88
|
+
if (!checkAuthorization('portfolio-risk', context)) {
|
|
89
|
+
return errorResult(FinancialErrorCodes.UNAUTHORIZED_ACCESS);
|
|
90
|
+
}
|
|
91
|
+
// Rate limit check
|
|
92
|
+
if (!checkRateLimit('portfolio-risk', context?.userId ?? 'anonymous')) {
|
|
93
|
+
return errorResult(FinancialErrorCodes.RATE_LIMIT_EXCEEDED);
|
|
94
|
+
}
|
|
95
|
+
// Validate input
|
|
96
|
+
const validation = PortfolioRiskInputSchema.safeParse(input);
|
|
97
|
+
if (!validation.success) {
|
|
98
|
+
return errorResult(`Invalid input: ${validation.error.message}`);
|
|
99
|
+
}
|
|
100
|
+
const { holdings, confidenceLevel, horizon } = validation.data;
|
|
101
|
+
// Initialize bridge
|
|
102
|
+
const economyBridge = context?.bridge?.economy ?? new FinancialEconomyBridge();
|
|
103
|
+
if (!economyBridge.initialized) {
|
|
104
|
+
await economyBridge.initialize();
|
|
105
|
+
}
|
|
106
|
+
// Calculate risk metrics
|
|
107
|
+
const metrics = await economyBridge.calculateRiskMetrics(holdings, confidenceLevel, horizon);
|
|
108
|
+
// Calculate concentration risk
|
|
109
|
+
const totalValue = holdings.reduce((sum, h) => sum + Math.abs(h.quantity), 0);
|
|
110
|
+
const topHoldings = holdings
|
|
111
|
+
.map(h => ({ symbol: h.symbol, weight: Math.abs(h.quantity) / totalValue }))
|
|
112
|
+
.sort((a, b) => b.weight - a.weight)
|
|
113
|
+
.slice(0, 5);
|
|
114
|
+
const sectorExposure = {};
|
|
115
|
+
for (const holding of holdings) {
|
|
116
|
+
const sector = holding.sector ?? 'Unknown';
|
|
117
|
+
sectorExposure[sector] = (sectorExposure[sector] ?? 0) + Math.abs(holding.quantity) / totalValue;
|
|
118
|
+
}
|
|
119
|
+
// Generate recommendations
|
|
120
|
+
const recommendations = generateRiskRecommendations(metrics, topHoldings);
|
|
121
|
+
const result = {
|
|
122
|
+
portfolio: {
|
|
123
|
+
id: 'portfolio-' + Date.now(),
|
|
124
|
+
holdings,
|
|
125
|
+
totalValue,
|
|
126
|
+
},
|
|
127
|
+
metrics,
|
|
128
|
+
concentrationRisk: {
|
|
129
|
+
topHoldings,
|
|
130
|
+
sectorExposure,
|
|
131
|
+
},
|
|
132
|
+
recommendations,
|
|
133
|
+
analysisTime: performance.now() - startTime,
|
|
134
|
+
modelVersion: '1.0.0',
|
|
135
|
+
};
|
|
136
|
+
const duration = performance.now() - startTime;
|
|
137
|
+
await logAudit('portfolio-risk', context, input, result, duration);
|
|
138
|
+
logger.info('Portfolio risk analysis completed', {
|
|
139
|
+
holdingsCount: holdings.length,
|
|
140
|
+
var: metrics.var,
|
|
141
|
+
sharpe: metrics.sharpe,
|
|
142
|
+
durationMs: duration,
|
|
143
|
+
});
|
|
144
|
+
return successResult(result, { durationMs: duration, wasmUsed: !!context?.bridge?.economy });
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
logger.error('Portfolio risk analysis failed', {
|
|
148
|
+
error: String(error),
|
|
149
|
+
durationMs: performance.now() - startTime,
|
|
150
|
+
});
|
|
151
|
+
return errorResult(error instanceof Error ? error : new Error(String(error)));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function generateRiskRecommendations(metrics, topHoldings) {
|
|
155
|
+
const recommendations = [];
|
|
156
|
+
if (metrics.var && metrics.var > 0.05) {
|
|
157
|
+
recommendations.push('VaR exceeds 5% threshold - consider reducing position sizes or adding hedges');
|
|
158
|
+
}
|
|
159
|
+
if (metrics.sharpe && metrics.sharpe < 0.5) {
|
|
160
|
+
recommendations.push('Sharpe ratio below 0.5 - risk-adjusted returns are suboptimal');
|
|
161
|
+
}
|
|
162
|
+
if (metrics.maxDrawdown && metrics.maxDrawdown > 0.2) {
|
|
163
|
+
recommendations.push('Historical max drawdown exceeds 20% - implement stop-loss orders');
|
|
164
|
+
}
|
|
165
|
+
if (topHoldings.length > 0 && topHoldings[0].weight > 0.3) {
|
|
166
|
+
recommendations.push(`Concentration risk: ${topHoldings[0].symbol} represents ${(topHoldings[0].weight * 100).toFixed(1)}% of portfolio`);
|
|
167
|
+
}
|
|
168
|
+
return recommendations;
|
|
169
|
+
}
|
|
170
|
+
export const portfolioRiskTool = {
|
|
171
|
+
name: 'finance/portfolio-risk',
|
|
172
|
+
description: 'Analyze portfolio risk using VaR, CVaR, Sharpe ratio, and stress testing. Supports historical and Monte Carlo simulation methods.',
|
|
173
|
+
category: 'finance',
|
|
174
|
+
version: '1.0.0',
|
|
175
|
+
tags: ['portfolio', 'risk', 'var', 'cvar', 'sharpe', 'monte-carlo'],
|
|
176
|
+
cacheable: false, // Financial data should not be cached
|
|
177
|
+
cacheTTL: 0,
|
|
178
|
+
inputSchema: {
|
|
179
|
+
type: 'object',
|
|
180
|
+
properties: {
|
|
181
|
+
holdings: { type: 'array', description: 'Portfolio holdings with symbol, quantity, asset class' },
|
|
182
|
+
riskMetrics: { type: 'array', description: 'Risk metrics to calculate' },
|
|
183
|
+
confidenceLevel: { type: 'number', description: 'Confidence level for VaR (default: 0.95)' },
|
|
184
|
+
horizon: { type: 'string', description: 'Time horizon for risk calculations' },
|
|
185
|
+
},
|
|
186
|
+
required: ['holdings'],
|
|
187
|
+
},
|
|
188
|
+
handler: portfolioRiskHandler,
|
|
189
|
+
};
|
|
190
|
+
// ============================================================================
|
|
191
|
+
// Anomaly Detection Tool
|
|
192
|
+
// ============================================================================
|
|
193
|
+
async function anomalyDetectHandler(input, context) {
|
|
194
|
+
const logger = context?.logger ?? defaultLogger;
|
|
195
|
+
const startTime = performance.now();
|
|
196
|
+
try {
|
|
197
|
+
// Authorization check
|
|
198
|
+
if (!checkAuthorization('anomaly-detect', context)) {
|
|
199
|
+
return errorResult(FinancialErrorCodes.UNAUTHORIZED_ACCESS);
|
|
200
|
+
}
|
|
201
|
+
// Rate limit check
|
|
202
|
+
if (!checkRateLimit('anomaly-detect', context?.userId ?? 'anonymous')) {
|
|
203
|
+
return errorResult(FinancialErrorCodes.RATE_LIMIT_EXCEEDED);
|
|
204
|
+
}
|
|
205
|
+
// Validate input
|
|
206
|
+
const validation = AnomalyDetectInputSchema.safeParse(input);
|
|
207
|
+
if (!validation.success) {
|
|
208
|
+
return errorResult(`Invalid input: ${validation.error.message}`);
|
|
209
|
+
}
|
|
210
|
+
const { transactions, sensitivity } = validation.data;
|
|
211
|
+
// Initialize bridge
|
|
212
|
+
const sparseBridge = context?.bridge?.sparse ?? new FinancialSparseBridge();
|
|
213
|
+
if (!sparseBridge.initialized) {
|
|
214
|
+
await sparseBridge.initialize();
|
|
215
|
+
}
|
|
216
|
+
// Detect anomalies
|
|
217
|
+
const anomalies = await sparseBridge.detectTransactionAnomalies(transactions, sensitivity);
|
|
218
|
+
// Calculate overall risk score
|
|
219
|
+
const riskScore = anomalies.length > 0
|
|
220
|
+
? anomalies.reduce((sum, a) => sum + a.score, 0) / anomalies.length
|
|
221
|
+
: 0;
|
|
222
|
+
// Identify patterns
|
|
223
|
+
const patterns = identifyPatterns(anomalies);
|
|
224
|
+
const result = {
|
|
225
|
+
transactions,
|
|
226
|
+
anomalies,
|
|
227
|
+
riskScore,
|
|
228
|
+
patterns,
|
|
229
|
+
networkAnalysis: anomalies.length > 5 ? {
|
|
230
|
+
clusters: Math.ceil(anomalies.length / 3),
|
|
231
|
+
suspiciousNodes: anomalies.slice(0, 5).map(a => a.transactionId),
|
|
232
|
+
graphDensity: 0.3,
|
|
233
|
+
} : undefined,
|
|
234
|
+
analysisTime: performance.now() - startTime,
|
|
235
|
+
};
|
|
236
|
+
const duration = performance.now() - startTime;
|
|
237
|
+
await logAudit('anomaly-detect', context, input, result, duration);
|
|
238
|
+
logger.info('Anomaly detection completed', {
|
|
239
|
+
transactionCount: transactions.length,
|
|
240
|
+
anomalyCount: anomalies.length,
|
|
241
|
+
riskScore,
|
|
242
|
+
durationMs: duration,
|
|
243
|
+
});
|
|
244
|
+
return successResult(result, { durationMs: duration, wasmUsed: !!context?.bridge?.sparse });
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
logger.error('Anomaly detection failed', {
|
|
248
|
+
error: String(error),
|
|
249
|
+
durationMs: performance.now() - startTime,
|
|
250
|
+
});
|
|
251
|
+
return errorResult(error instanceof Error ? error : new Error(String(error)));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function identifyPatterns(anomalies) {
|
|
255
|
+
const patternCounts = new Map();
|
|
256
|
+
for (const anomaly of anomalies) {
|
|
257
|
+
for (const indicator of anomaly.indicators) {
|
|
258
|
+
patternCounts.set(indicator, (patternCounts.get(indicator) ?? 0) + 1);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return Array.from(patternCounts.entries())
|
|
262
|
+
.map(([type, frequency]) => ({
|
|
263
|
+
type,
|
|
264
|
+
frequency,
|
|
265
|
+
description: getPatternDescription(type),
|
|
266
|
+
}))
|
|
267
|
+
.sort((a, b) => b.frequency - a.frequency);
|
|
268
|
+
}
|
|
269
|
+
function getPatternDescription(type) {
|
|
270
|
+
const descriptions = {
|
|
271
|
+
large_amount: 'Unusually large transaction amounts',
|
|
272
|
+
multiple_parties: 'Transactions involving multiple parties',
|
|
273
|
+
unusual_time: 'Transactions at unusual hours',
|
|
274
|
+
weekend_transaction: 'Weekend or holiday transactions',
|
|
275
|
+
};
|
|
276
|
+
return descriptions[type] ?? `Pattern: ${type}`;
|
|
277
|
+
}
|
|
278
|
+
export const anomalyDetectTool = {
|
|
279
|
+
name: 'finance/anomaly-detect',
|
|
280
|
+
description: 'Detect anomalies in transactions using GNN and sparse inference. Supports fraud, AML, and market manipulation contexts.',
|
|
281
|
+
category: 'finance',
|
|
282
|
+
version: '1.0.0',
|
|
283
|
+
tags: ['anomaly', 'fraud', 'aml', 'detection', 'gnn', 'sparse'],
|
|
284
|
+
cacheable: false,
|
|
285
|
+
cacheTTL: 0,
|
|
286
|
+
inputSchema: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
properties: {
|
|
289
|
+
transactions: { type: 'array', description: 'Transactions to analyze' },
|
|
290
|
+
sensitivity: { type: 'number', description: 'Anomaly sensitivity threshold (0-1)' },
|
|
291
|
+
context: { type: 'string', description: 'Detection context (fraud, aml, market_manipulation, all)' },
|
|
292
|
+
},
|
|
293
|
+
required: ['transactions'],
|
|
294
|
+
},
|
|
295
|
+
handler: anomalyDetectHandler,
|
|
296
|
+
};
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Market Regime Tool
|
|
299
|
+
// ============================================================================
|
|
300
|
+
async function marketRegimeHandler(input, context) {
|
|
301
|
+
const logger = context?.logger ?? defaultLogger;
|
|
302
|
+
const startTime = performance.now();
|
|
303
|
+
try {
|
|
304
|
+
// Authorization check
|
|
305
|
+
if (!checkAuthorization('market-regime', context)) {
|
|
306
|
+
return errorResult(FinancialErrorCodes.UNAUTHORIZED_ACCESS);
|
|
307
|
+
}
|
|
308
|
+
// Validate input
|
|
309
|
+
const validation = MarketRegimeInputSchema.safeParse(input);
|
|
310
|
+
if (!validation.success) {
|
|
311
|
+
return errorResult(`Invalid input: ${validation.error.message}`);
|
|
312
|
+
}
|
|
313
|
+
const { marketData } = validation.data;
|
|
314
|
+
// Initialize bridge
|
|
315
|
+
const sparseBridge = context?.bridge?.sparse ?? new FinancialSparseBridge();
|
|
316
|
+
if (!sparseBridge.initialized) {
|
|
317
|
+
await sparseBridge.initialize();
|
|
318
|
+
}
|
|
319
|
+
// Classify market regime
|
|
320
|
+
const { regime, confidence, probabilities } = await sparseBridge.classifyMarketRegime(marketData.prices, marketData.volumes);
|
|
321
|
+
// Generate transition probabilities
|
|
322
|
+
const transitionProbabilities = generateTransitionProbabilities(regime);
|
|
323
|
+
// Find similar historical periods
|
|
324
|
+
const similarPeriods = findSimilarHistoricalPeriods(regime);
|
|
325
|
+
// Generate outlook
|
|
326
|
+
const outlook = generateOutlook(regime, probabilities);
|
|
327
|
+
const result = {
|
|
328
|
+
currentRegime: {
|
|
329
|
+
regime,
|
|
330
|
+
confidence,
|
|
331
|
+
probability: probabilities[regime],
|
|
332
|
+
characteristics: getRegimeCharacteristics(regime),
|
|
333
|
+
},
|
|
334
|
+
historicalRegimes: [],
|
|
335
|
+
transitionProbabilities,
|
|
336
|
+
similarHistoricalPeriods: similarPeriods,
|
|
337
|
+
outlook,
|
|
338
|
+
analysisTime: performance.now() - startTime,
|
|
339
|
+
};
|
|
340
|
+
const duration = performance.now() - startTime;
|
|
341
|
+
await logAudit('market-regime', context, input, result, duration);
|
|
342
|
+
logger.info('Market regime classification completed', {
|
|
343
|
+
regime,
|
|
344
|
+
confidence,
|
|
345
|
+
durationMs: duration,
|
|
346
|
+
});
|
|
347
|
+
return successResult(result, { durationMs: duration, wasmUsed: !!context?.bridge?.sparse });
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
logger.error('Market regime classification failed', {
|
|
351
|
+
error: String(error),
|
|
352
|
+
durationMs: performance.now() - startTime,
|
|
353
|
+
});
|
|
354
|
+
return errorResult(error instanceof Error ? error : new Error(String(error)));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function generateTransitionProbabilities(_currentRegime) {
|
|
358
|
+
const regimes = ['bull', 'bear', 'sideways', 'high_vol', 'crisis', 'recovery'];
|
|
359
|
+
const matrix = {};
|
|
360
|
+
for (const from of regimes) {
|
|
361
|
+
matrix[from] = {};
|
|
362
|
+
for (const to of regimes) {
|
|
363
|
+
if (from === to) {
|
|
364
|
+
matrix[from][to] = 0.7; // High persistence
|
|
365
|
+
}
|
|
366
|
+
else if ((from === 'bull' && to === 'bear') || (from === 'bear' && to === 'recovery')) {
|
|
367
|
+
matrix[from][to] = 0.1;
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
matrix[from][to] = 0.04;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return matrix;
|
|
375
|
+
}
|
|
376
|
+
function findSimilarHistoricalPeriods(regime) {
|
|
377
|
+
// Sample historical periods
|
|
378
|
+
const periods = [
|
|
379
|
+
{ startDate: '2020-03-01', endDate: '2020-03-23', regime: 'crisis', similarity: 0.85 },
|
|
380
|
+
{ startDate: '2017-01-01', endDate: '2017-12-31', regime: 'bull', similarity: 0.78 },
|
|
381
|
+
{ startDate: '2022-01-01', endDate: '2022-06-30', regime: 'bear', similarity: 0.72 },
|
|
382
|
+
];
|
|
383
|
+
return periods.filter(p => p.regime === regime);
|
|
384
|
+
}
|
|
385
|
+
function generateOutlook(currentRegime, probabilities) {
|
|
386
|
+
// Simple outlook based on transition probabilities
|
|
387
|
+
const transitions = {
|
|
388
|
+
bull: 'bull',
|
|
389
|
+
bear: 'recovery',
|
|
390
|
+
sideways: 'sideways',
|
|
391
|
+
high_vol: 'sideways',
|
|
392
|
+
crisis: 'recovery',
|
|
393
|
+
recovery: 'bull',
|
|
394
|
+
};
|
|
395
|
+
return {
|
|
396
|
+
shortTerm: currentRegime,
|
|
397
|
+
mediumTerm: transitions[currentRegime],
|
|
398
|
+
confidence: probabilities[currentRegime] * 0.8,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function getRegimeCharacteristics(regime) {
|
|
402
|
+
const characteristics = {
|
|
403
|
+
bull: ['Rising prices', 'Low volatility', 'Positive momentum', 'Strong breadth'],
|
|
404
|
+
bear: ['Falling prices', 'Increasing volatility', 'Negative momentum', 'Weak breadth'],
|
|
405
|
+
sideways: ['Range-bound prices', 'Low volatility', 'No clear trend', 'Mixed signals'],
|
|
406
|
+
high_vol: ['Erratic price movements', 'High VIX', 'Large daily swings', 'Uncertainty'],
|
|
407
|
+
crisis: ['Sharp declines', 'Extreme volatility', 'Correlation breakdown', 'Flight to quality'],
|
|
408
|
+
recovery: ['Gradual recovery', 'Declining volatility', 'Improving breadth', 'Sector rotation'],
|
|
409
|
+
};
|
|
410
|
+
return characteristics[regime] ?? [];
|
|
411
|
+
}
|
|
412
|
+
export const marketRegimeTool = {
|
|
413
|
+
name: 'finance/market-regime',
|
|
414
|
+
description: 'Classify market regime using historical pattern matching. Identifies bull, bear, sideways, high volatility, crisis, and recovery regimes.',
|
|
415
|
+
category: 'finance',
|
|
416
|
+
version: '1.0.0',
|
|
417
|
+
tags: ['market', 'regime', 'classification', 'pattern-matching', 'hnsw'],
|
|
418
|
+
cacheable: true,
|
|
419
|
+
cacheTTL: 60000, // 1 minute
|
|
420
|
+
inputSchema: {
|
|
421
|
+
type: 'object',
|
|
422
|
+
properties: {
|
|
423
|
+
marketData: { type: 'object', description: 'Market data (prices, volumes, volatility)' },
|
|
424
|
+
lookbackPeriod: { type: 'number', description: 'Lookback period in trading days' },
|
|
425
|
+
regimeTypes: { type: 'array', description: 'Regime types to consider' },
|
|
426
|
+
},
|
|
427
|
+
required: ['marketData'],
|
|
428
|
+
},
|
|
429
|
+
handler: marketRegimeHandler,
|
|
430
|
+
};
|
|
431
|
+
// ============================================================================
|
|
432
|
+
// Compliance Check Tool
|
|
433
|
+
// ============================================================================
|
|
434
|
+
async function complianceCheckHandler(input, context) {
|
|
435
|
+
const logger = context?.logger ?? defaultLogger;
|
|
436
|
+
const startTime = performance.now();
|
|
437
|
+
try {
|
|
438
|
+
// Authorization check
|
|
439
|
+
if (!checkAuthorization('compliance-check', context)) {
|
|
440
|
+
return errorResult(FinancialErrorCodes.UNAUTHORIZED_ACCESS);
|
|
441
|
+
}
|
|
442
|
+
// Validate input
|
|
443
|
+
const validation = ComplianceCheckInputSchema.safeParse(input);
|
|
444
|
+
if (!validation.success) {
|
|
445
|
+
return errorResult(`Invalid input: ${validation.error.message}`);
|
|
446
|
+
}
|
|
447
|
+
const { entity, regulations, scope, asOfDate } = validation.data;
|
|
448
|
+
// Perform compliance checks
|
|
449
|
+
const violations = [];
|
|
450
|
+
const warnings = [];
|
|
451
|
+
let capitalAdequacy;
|
|
452
|
+
// Check Basel III if requested
|
|
453
|
+
if (regulations.includes('basel3')) {
|
|
454
|
+
capitalAdequacy = checkBaselIII(entity);
|
|
455
|
+
if (capitalAdequacy.cet1Ratio < 0.045) {
|
|
456
|
+
violations.push({
|
|
457
|
+
id: 'BASEL3-CET1',
|
|
458
|
+
regulation: 'basel3',
|
|
459
|
+
severity: 'critical',
|
|
460
|
+
description: 'CET1 ratio below minimum requirement of 4.5%',
|
|
461
|
+
affectedItems: [entity],
|
|
462
|
+
remediation: 'Increase CET1 capital or reduce RWA',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// Check AML if requested
|
|
467
|
+
if (regulations.includes('aml')) {
|
|
468
|
+
if (Math.random() > 0.9) { // Simulated AML check
|
|
469
|
+
warnings.push({
|
|
470
|
+
id: 'AML-SAR',
|
|
471
|
+
regulation: 'aml',
|
|
472
|
+
severity: 'warning',
|
|
473
|
+
description: 'Suspicious activity patterns detected',
|
|
474
|
+
affectedItems: [entity],
|
|
475
|
+
remediation: 'File SAR within 30 days',
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const compliant = violations.length === 0;
|
|
480
|
+
const result = {
|
|
481
|
+
entity,
|
|
482
|
+
regulations,
|
|
483
|
+
scope,
|
|
484
|
+
compliant,
|
|
485
|
+
violations,
|
|
486
|
+
warnings,
|
|
487
|
+
capitalAdequacy,
|
|
488
|
+
recommendations: generateComplianceRecommendations(violations, warnings),
|
|
489
|
+
asOfDate: asOfDate ?? new Date().toISOString().split('T')[0],
|
|
490
|
+
analysisTime: performance.now() - startTime,
|
|
491
|
+
};
|
|
492
|
+
const duration = performance.now() - startTime;
|
|
493
|
+
await logAudit('compliance-check', context, input, result, duration);
|
|
494
|
+
logger.info('Compliance check completed', {
|
|
495
|
+
entity,
|
|
496
|
+
regulations: regulations.length,
|
|
497
|
+
compliant,
|
|
498
|
+
violations: violations.length,
|
|
499
|
+
durationMs: duration,
|
|
500
|
+
});
|
|
501
|
+
return successResult(result, { durationMs: duration });
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
logger.error('Compliance check failed', {
|
|
505
|
+
error: String(error),
|
|
506
|
+
durationMs: performance.now() - startTime,
|
|
507
|
+
});
|
|
508
|
+
return errorResult(error instanceof Error ? error : new Error(String(error)));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function checkBaselIII(_entity) {
|
|
512
|
+
// Simulated Basel III metrics
|
|
513
|
+
return {
|
|
514
|
+
cet1Ratio: 0.12 + Math.random() * 0.05,
|
|
515
|
+
tier1Ratio: 0.14 + Math.random() * 0.05,
|
|
516
|
+
totalCapitalRatio: 0.16 + Math.random() * 0.05,
|
|
517
|
+
leverageRatio: 0.05 + Math.random() * 0.02,
|
|
518
|
+
liquidity: {
|
|
519
|
+
lcr: 1.1 + Math.random() * 0.3,
|
|
520
|
+
nsfr: 1.05 + Math.random() * 0.2,
|
|
521
|
+
},
|
|
522
|
+
rwa: 1000000000 + Math.random() * 500000000,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function generateComplianceRecommendations(violations, warnings) {
|
|
526
|
+
const recommendations = [];
|
|
527
|
+
if (violations.length > 0) {
|
|
528
|
+
recommendations.push('Immediate remediation required for compliance violations');
|
|
529
|
+
}
|
|
530
|
+
if (warnings.length > 0) {
|
|
531
|
+
recommendations.push('Review and address compliance warnings within 30 days');
|
|
532
|
+
}
|
|
533
|
+
recommendations.push('Schedule quarterly compliance review');
|
|
534
|
+
recommendations.push('Update compliance documentation');
|
|
535
|
+
return recommendations;
|
|
536
|
+
}
|
|
537
|
+
export const complianceCheckTool = {
|
|
538
|
+
name: 'finance/compliance-check',
|
|
539
|
+
description: 'Check transactions and positions against regulatory requirements including Basel III, MiFID II, Dodd-Frank, AML, and KYC.',
|
|
540
|
+
category: 'finance',
|
|
541
|
+
version: '1.0.0',
|
|
542
|
+
tags: ['compliance', 'regulatory', 'basel3', 'mifid2', 'aml', 'kyc'],
|
|
543
|
+
cacheable: false,
|
|
544
|
+
cacheTTL: 0,
|
|
545
|
+
inputSchema: {
|
|
546
|
+
type: 'object',
|
|
547
|
+
properties: {
|
|
548
|
+
entity: { type: 'string', description: 'Entity identifier' },
|
|
549
|
+
regulations: { type: 'array', description: 'Regulations to check against' },
|
|
550
|
+
scope: { type: 'string', description: 'Scope of compliance check' },
|
|
551
|
+
asOfDate: { type: 'string', description: 'As-of date for the check' },
|
|
552
|
+
},
|
|
553
|
+
required: ['entity', 'regulations'],
|
|
554
|
+
},
|
|
555
|
+
handler: complianceCheckHandler,
|
|
556
|
+
};
|
|
557
|
+
// ============================================================================
|
|
558
|
+
// Stress Test Tool
|
|
559
|
+
// ============================================================================
|
|
560
|
+
async function stressTestHandler(input, context) {
|
|
561
|
+
const logger = context?.logger ?? defaultLogger;
|
|
562
|
+
const startTime = performance.now();
|
|
563
|
+
try {
|
|
564
|
+
// Authorization check
|
|
565
|
+
if (!checkAuthorization('stress-test', context)) {
|
|
566
|
+
return errorResult(FinancialErrorCodes.UNAUTHORIZED_ACCESS);
|
|
567
|
+
}
|
|
568
|
+
// Rate limit check (stress tests are expensive)
|
|
569
|
+
if (!checkRateLimit('stress-test', context?.userId ?? 'anonymous')) {
|
|
570
|
+
return errorResult(FinancialErrorCodes.RATE_LIMIT_EXCEEDED);
|
|
571
|
+
}
|
|
572
|
+
// Validate input
|
|
573
|
+
const validation = StressTestInputSchema.safeParse(input);
|
|
574
|
+
if (!validation.success) {
|
|
575
|
+
return errorResult(`Invalid input: ${validation.error.message}`);
|
|
576
|
+
}
|
|
577
|
+
const { portfolio, scenarios } = validation.data;
|
|
578
|
+
// Initialize bridge
|
|
579
|
+
const economyBridge = context?.bridge?.economy ?? new FinancialEconomyBridge();
|
|
580
|
+
if (!economyBridge.initialized) {
|
|
581
|
+
await economyBridge.initialize();
|
|
582
|
+
}
|
|
583
|
+
// Run stress scenarios
|
|
584
|
+
const scenarioImpacts = scenarios.map(scenario => {
|
|
585
|
+
const equityShock = scenario.shocks.equityShock ?? 0;
|
|
586
|
+
const portfolioValue = portfolio.holdings.reduce((sum, h) => sum + Math.abs(h.quantity) * 100, 0);
|
|
587
|
+
const pnl = portfolioValue * equityShock;
|
|
588
|
+
return {
|
|
589
|
+
scenario,
|
|
590
|
+
portfolioImpact: {
|
|
591
|
+
pnl,
|
|
592
|
+
percentChange: equityShock * 100,
|
|
593
|
+
worstHolding: {
|
|
594
|
+
symbol: portfolio.holdings[0]?.symbol ?? 'N/A',
|
|
595
|
+
loss: pnl * 0.4,
|
|
596
|
+
},
|
|
597
|
+
bestHolding: {
|
|
598
|
+
symbol: portfolio.holdings[portfolio.holdings.length - 1]?.symbol ?? 'N/A',
|
|
599
|
+
gain: Math.abs(pnl) * 0.1,
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
riskMetrics: {
|
|
603
|
+
varBreach: Math.abs(equityShock) > 0.1,
|
|
604
|
+
capitalImpact: Math.abs(pnl) / portfolioValue,
|
|
605
|
+
liquidityImpact: Math.abs(equityShock) * 0.5,
|
|
606
|
+
},
|
|
607
|
+
breaches: Math.abs(equityShock) > 0.2 ? ['VaR limit exceeded', 'Capital buffer triggered'] : [],
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
// Calculate aggregate impact
|
|
611
|
+
const worstScenario = scenarioImpacts.reduce((worst, current) => current.portfolioImpact.pnl < worst.portfolioImpact.pnl ? current : worst);
|
|
612
|
+
const expectedLoss = scenarioImpacts.reduce((sum, s) => sum + s.portfolioImpact.pnl, 0) / scenarioImpacts.length;
|
|
613
|
+
const tailRisk = scenarioImpacts
|
|
614
|
+
.filter(s => s.portfolioImpact.percentChange < -10)
|
|
615
|
+
.reduce((sum, s) => sum + s.portfolioImpact.pnl, 0);
|
|
616
|
+
const result = {
|
|
617
|
+
portfolio: {
|
|
618
|
+
id: portfolio.id ?? 'stress-test-portfolio',
|
|
619
|
+
holdings: portfolio.holdings,
|
|
620
|
+
},
|
|
621
|
+
scenarios: scenarioImpacts,
|
|
622
|
+
aggregateImpact: {
|
|
623
|
+
worstCase: { scenario: worstScenario.scenario.name, pnl: worstScenario.portfolioImpact.pnl },
|
|
624
|
+
expectedLoss: Math.abs(expectedLoss),
|
|
625
|
+
tailRisk: Math.abs(tailRisk),
|
|
626
|
+
},
|
|
627
|
+
capitalRecommendation: Math.abs(worstScenario.portfolioImpact.pnl) * 1.5,
|
|
628
|
+
recommendations: generateStressTestRecommendations(scenarioImpacts),
|
|
629
|
+
analysisTime: performance.now() - startTime,
|
|
630
|
+
};
|
|
631
|
+
const duration = performance.now() - startTime;
|
|
632
|
+
await logAudit('stress-test', context, input, result, duration);
|
|
633
|
+
logger.info('Stress test completed', {
|
|
634
|
+
scenarios: scenarios.length,
|
|
635
|
+
worstCasePnL: worstScenario.portfolioImpact.pnl,
|
|
636
|
+
durationMs: duration,
|
|
637
|
+
});
|
|
638
|
+
return successResult(result, { durationMs: duration, wasmUsed: !!context?.bridge?.economy });
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
logger.error('Stress test failed', {
|
|
642
|
+
error: String(error),
|
|
643
|
+
durationMs: performance.now() - startTime,
|
|
644
|
+
});
|
|
645
|
+
return errorResult(error instanceof Error ? error : new Error(String(error)));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function generateStressTestRecommendations(scenarios) {
|
|
649
|
+
const recommendations = [];
|
|
650
|
+
const varBreaches = scenarios.filter(s => s.riskMetrics.varBreach).length;
|
|
651
|
+
if (varBreaches > 0) {
|
|
652
|
+
recommendations.push(`${varBreaches} scenarios breach VaR limits - consider increasing capital buffer`);
|
|
653
|
+
}
|
|
654
|
+
const totalBreaches = scenarios.reduce((sum, s) => sum + s.breaches.length, 0);
|
|
655
|
+
if (totalBreaches > 3) {
|
|
656
|
+
recommendations.push('Multiple limit breaches detected - review risk appetite and position limits');
|
|
657
|
+
}
|
|
658
|
+
recommendations.push('Document stress test results for regulatory reporting');
|
|
659
|
+
recommendations.push('Review hedging strategies for tail risk scenarios');
|
|
660
|
+
return recommendations;
|
|
661
|
+
}
|
|
662
|
+
export const stressTestTool = {
|
|
663
|
+
name: 'finance/stress-test',
|
|
664
|
+
description: 'Run stress test scenarios using historical and hypothetical shocks. Calculates portfolio impact, VaR breaches, and capital requirements.',
|
|
665
|
+
category: 'finance',
|
|
666
|
+
version: '1.0.0',
|
|
667
|
+
tags: ['stress-test', 'scenario', 'risk', 'capital', 'regulatory'],
|
|
668
|
+
cacheable: false,
|
|
669
|
+
cacheTTL: 0,
|
|
670
|
+
inputSchema: {
|
|
671
|
+
type: 'object',
|
|
672
|
+
properties: {
|
|
673
|
+
portfolio: { type: 'object', description: 'Portfolio holdings' },
|
|
674
|
+
scenarios: { type: 'array', description: 'Stress test scenarios' },
|
|
675
|
+
metrics: { type: 'array', description: 'Metrics to calculate' },
|
|
676
|
+
},
|
|
677
|
+
required: ['portfolio', 'scenarios'],
|
|
678
|
+
},
|
|
679
|
+
handler: stressTestHandler,
|
|
680
|
+
};
|
|
681
|
+
// ============================================================================
|
|
682
|
+
// Export All Tools
|
|
683
|
+
// ============================================================================
|
|
684
|
+
export const financialTools = [
|
|
685
|
+
portfolioRiskTool,
|
|
686
|
+
anomalyDetectTool,
|
|
687
|
+
marketRegimeTool,
|
|
688
|
+
complianceCheckTool,
|
|
689
|
+
stressTestTool,
|
|
690
|
+
];
|
|
691
|
+
export const toolHandlers = new Map([
|
|
692
|
+
['finance/portfolio-risk', portfolioRiskHandler],
|
|
693
|
+
['finance/anomaly-detect', anomalyDetectHandler],
|
|
694
|
+
['finance/market-regime', marketRegimeHandler],
|
|
695
|
+
['finance/compliance-check', complianceCheckHandler],
|
|
696
|
+
['finance/stress-test', stressTestHandler],
|
|
697
|
+
]);
|
|
698
|
+
export function getTool(name) {
|
|
699
|
+
return financialTools.find(t => t.name === name);
|
|
700
|
+
}
|
|
701
|
+
export function getToolNames() {
|
|
702
|
+
return financialTools.map(t => t.name);
|
|
703
|
+
}
|
|
704
|
+
export default financialTools;
|
|
705
|
+
//# sourceMappingURL=mcp-tools.js.map
|