@autonomaai/agent-core 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/base-agent.d.ts +112 -0
  2. package/dist/base-agent.d.ts.map +1 -0
  3. package/dist/base-agent.js +173 -0
  4. package/dist/base-agent.js.map +1 -0
  5. package/dist/core.d.ts +81 -0
  6. package/dist/core.d.ts.map +1 -0
  7. package/dist/core.js +633 -0
  8. package/dist/core.js.map +1 -0
  9. package/dist/error-handler.d.ts +78 -0
  10. package/dist/error-handler.d.ts.map +1 -0
  11. package/dist/error-handler.js +129 -0
  12. package/dist/error-handler.js.map +1 -0
  13. package/dist/factory.d.ts +60 -0
  14. package/dist/factory.d.ts.map +1 -0
  15. package/dist/factory.js +621 -0
  16. package/dist/factory.js.map +1 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +19 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/streaming.d.ts +24 -0
  22. package/dist/streaming.d.ts.map +1 -0
  23. package/dist/streaming.js +36 -0
  24. package/dist/streaming.js.map +1 -0
  25. package/dist/trading/formatters.d.ts +167 -0
  26. package/dist/trading/formatters.d.ts.map +1 -0
  27. package/dist/trading/formatters.js +271 -0
  28. package/dist/trading/formatters.js.map +1 -0
  29. package/dist/trading/index.d.ts +9 -0
  30. package/dist/trading/index.d.ts.map +1 -0
  31. package/dist/trading/index.js +10 -0
  32. package/dist/trading/index.js.map +1 -0
  33. package/dist/trading/types.d.ts +205 -0
  34. package/dist/trading/types.d.ts.map +1 -0
  35. package/dist/trading/types.js +7 -0
  36. package/dist/trading/types.js.map +1 -0
  37. package/dist/trading/utils.d.ts +120 -0
  38. package/dist/trading/utils.d.ts.map +1 -0
  39. package/dist/trading/utils.js +291 -0
  40. package/dist/trading/utils.js.map +1 -0
  41. package/dist/trading/validation.d.ts +40 -0
  42. package/dist/trading/validation.d.ts.map +1 -0
  43. package/dist/trading/validation.js +247 -0
  44. package/dist/trading/validation.js.map +1 -0
  45. package/dist/types.d.ts +282 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +21 -0
  48. package/dist/types.js.map +1 -0
  49. package/package.json +57 -0
  50. package/src/base-agent.ts +263 -0
  51. package/src/core.ts +792 -0
  52. package/src/error-handler.ts +166 -0
  53. package/src/factory.ts +687 -0
  54. package/src/global.d.ts +12 -0
  55. package/src/index.ts +24 -0
  56. package/src/streaming.ts +50 -0
  57. package/src/trading/formatters.ts +363 -0
  58. package/src/trading/index.ts +10 -0
  59. package/src/trading/types.ts +263 -0
  60. package/src/trading/utils.ts +355 -0
  61. package/src/trading/validation.ts +321 -0
  62. package/src/types.ts +402 -0
@@ -0,0 +1,355 @@
1
+ /**
2
+ * Trading Utilities for autonoma Agent Core
3
+ *
4
+ * Common utility functions for trading calculations and operations.
5
+ */
6
+
7
+ import {
8
+ Order,
9
+ Position,
10
+ Portfolio,
11
+ OrderSide,
12
+ YieldOpportunity,
13
+ TimeFrame,
14
+ PriceRange,
15
+ OHLCV
16
+ } from './types.js';
17
+
18
+ // =============================================================================
19
+ // Price & Market Calculations
20
+ // =============================================================================
21
+
22
+ /**
23
+ * Calculate percentage change between two prices
24
+ */
25
+ export function calculatePriceChange(oldPrice: number, newPrice: number): number {
26
+ if (oldPrice === 0) return 0;
27
+ return ((newPrice - oldPrice) / oldPrice) * 100;
28
+ }
29
+
30
+ /**
31
+ * Calculate simple moving average
32
+ */
33
+ export function calculateSMA(prices: number[], period: number): number {
34
+ if (prices.length < period) return 0;
35
+ const sum = prices.slice(-period).reduce((acc, price) => acc + price, 0);
36
+ return sum / period;
37
+ }
38
+
39
+ /**
40
+ * Calculate exponential moving average
41
+ */
42
+ export function calculateEMA(prices: number[], period: number): number {
43
+ if (prices.length === 0) return 0;
44
+ if (prices.length === 1) return prices[0]!;
45
+
46
+ const multiplier = 2 / (period + 1);
47
+ let ema = prices[0]!;
48
+
49
+ for (let i = 1; i < prices.length; i++) {
50
+ const price = prices[i];
51
+ if (price !== undefined) {
52
+ ema = (price * multiplier) + (ema * (1 - multiplier));
53
+ }
54
+ }
55
+
56
+ return ema;
57
+ }
58
+
59
+ /**
60
+ * Calculate volatility (standard deviation) of price returns
61
+ */
62
+ export function calculateVolatility(prices: number[]): number {
63
+ if (prices.length < 2) return 0;
64
+
65
+ const returns = [];
66
+ for (let i = 1; i < prices.length; i++) {
67
+ const current = prices[i];
68
+ const previous = prices[i - 1];
69
+ if (current !== undefined && previous !== undefined && previous !== 0) {
70
+ returns.push((current - previous) / previous);
71
+ }
72
+ }
73
+
74
+ const mean = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
75
+ const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - mean, 2), 0) / returns.length;
76
+
77
+ return Math.sqrt(variance) * Math.sqrt(252); // Annualized volatility
78
+ }
79
+
80
+ /**
81
+ * Calculate support and resistance levels
82
+ */
83
+ export function findSupportResistance(ohlcv: OHLCV[], lookback: number = 20): { support: number; resistance: number } {
84
+ if (ohlcv.length < lookback) return { support: 0, resistance: 0 };
85
+
86
+ const recent = ohlcv.slice(-lookback);
87
+ const highs = recent.map(candle => candle.high);
88
+ const lows = recent.map(candle => candle.low);
89
+
90
+ const support = lows.reduce((min, low) => Math.min(min, low), lows[0] ?? 0);
91
+ const resistance = highs.reduce((max, high) => Math.max(max, high), highs[0] ?? 0);
92
+
93
+ return { support, resistance };
94
+ }
95
+
96
+ // =============================================================================
97
+ // Portfolio & Risk Calculations
98
+ // =============================================================================
99
+
100
+ /**
101
+ * Calculate portfolio total value
102
+ */
103
+ export function calculatePortfolioValue(portfolio: Portfolio): number {
104
+ const positionsValue = portfolio.positions.reduce((total, position) => {
105
+ return total + position.marketValue;
106
+ }, 0);
107
+
108
+ return portfolio.cash + positionsValue;
109
+ }
110
+
111
+ /**
112
+ * Calculate position size as percentage of portfolio
113
+ */
114
+ export function calculatePositionWeight(position: Position, portfolioValue: number): number {
115
+ if (portfolioValue === 0) return 0;
116
+ return (position.marketValue / portfolioValue) * 100;
117
+ }
118
+
119
+ /**
120
+ * Calculate unrealized P&L for a position
121
+ */
122
+ export function calculateUnrealizedPnL(position: Position, currentPrice: number): number {
123
+ return (currentPrice - position.averagePrice) * position.quantity;
124
+ }
125
+
126
+ /**
127
+ * Calculate Sharpe ratio
128
+ */
129
+ export function calculateSharpeRatio(returns: number[], riskFreeRate: number = 0.02): number {
130
+ if (returns.length === 0) return 0;
131
+
132
+ const avgReturn = returns.reduce((sum, ret) => sum + ret, 0) / returns.length;
133
+ const variance = returns.reduce((sum, ret) => sum + Math.pow(ret - avgReturn, 2), 0) / returns.length;
134
+ const stdDev = Math.sqrt(variance);
135
+
136
+ if (stdDev === 0) return 0;
137
+ return (avgReturn - riskFreeRate) / stdDev;
138
+ }
139
+
140
+ /**
141
+ * Calculate maximum drawdown
142
+ */
143
+ export function calculateMaxDrawdown(portfolioValues: number[]): number {
144
+ if (portfolioValues.length < 2) return 0;
145
+
146
+ let maxDrawdown = 0;
147
+ let peak = portfolioValues[0] ?? 0;
148
+
149
+ for (let i = 1; i < portfolioValues.length; i++) {
150
+ const value = portfolioValues[i];
151
+ if (value !== undefined) {
152
+ if (value > peak) {
153
+ peak = value;
154
+ } else if (peak > 0) {
155
+ const drawdown = (peak - value) / peak;
156
+ maxDrawdown = Math.max(maxDrawdown, drawdown);
157
+ }
158
+ }
159
+ }
160
+
161
+ return maxDrawdown * 100; // Return as percentage
162
+ }
163
+
164
+ /**
165
+ * Calculate Value at Risk (VaR) using historical simulation
166
+ */
167
+ export function calculateVaR(returns: number[], confidence: number = 0.05): number {
168
+ if (returns.length === 0) return 0;
169
+
170
+ const sortedReturns = [...returns].sort((a, b) => a - b);
171
+ const index = Math.floor(returns.length * confidence);
172
+
173
+ return Math.abs(sortedReturns[index] || 0);
174
+ }
175
+
176
+ // =============================================================================
177
+ // Order & Trading Calculations
178
+ // =============================================================================
179
+
180
+ /**
181
+ * Calculate order fill ratio
182
+ */
183
+ export function calculateFillRatio(order: Order): number {
184
+ if (order.quantity === 0) return 0;
185
+ return (order.filledQuantity / order.quantity) * 100;
186
+ }
187
+
188
+ /**
189
+ * Calculate slippage
190
+ */
191
+ export function calculateSlippage(expectedPrice: number, executedPrice: number, side: OrderSide): number {
192
+ if (expectedPrice === 0) return 0;
193
+
194
+ const diff = side === 'buy'
195
+ ? executedPrice - expectedPrice
196
+ : expectedPrice - executedPrice;
197
+
198
+ return (diff / expectedPrice) * 100;
199
+ }
200
+
201
+ /**
202
+ * Calculate optimal order size based on market impact
203
+ */
204
+ export function calculateOptimalOrderSize(
205
+ availableLiquidity: number,
206
+ targetSize: number,
207
+ maxMarketImpact: number = 0.5 // 0.5% max impact
208
+ ): number {
209
+ const maxSafeSize = availableLiquidity * (maxMarketImpact / 100);
210
+ return Math.min(targetSize, maxSafeSize);
211
+ }
212
+
213
+ // =============================================================================
214
+ // DeFi Calculations
215
+ // =============================================================================
216
+
217
+ /**
218
+ * Calculate APY from APR
219
+ */
220
+ export function aprToApy(apr: number, compoundingPeriods: number = 365): number {
221
+ return (Math.pow(1 + (apr / compoundingPeriods), compoundingPeriods) - 1) * 100;
222
+ }
223
+
224
+ /**
225
+ * Calculate yield farming returns
226
+ */
227
+ export function calculateYieldFarmingReturns(
228
+ principal: number,
229
+ apy: number,
230
+ days: number,
231
+ compounding: boolean = true
232
+ ): { totalReturn: number; profit: number } {
233
+ const periods = compounding ? days : 1;
234
+ const rate = apy / 100 / (compounding ? 365 : 1);
235
+
236
+ const totalReturn = compounding
237
+ ? principal * Math.pow(1 + rate, periods)
238
+ : principal + (principal * rate * days);
239
+
240
+ return {
241
+ totalReturn,
242
+ profit: totalReturn - principal
243
+ };
244
+ }
245
+
246
+ /**
247
+ * Calculate impermanent loss for liquidity provision
248
+ */
249
+ export function calculateImpermanentLoss(
250
+ initialPrice1: number,
251
+ initialPrice2: number,
252
+ currentPrice1: number,
253
+ currentPrice2: number
254
+ ): number {
255
+ const priceRatio = (currentPrice1 / currentPrice2) / (initialPrice1 / initialPrice2);
256
+ const impermanentLoss = (2 * Math.sqrt(priceRatio)) / (1 + priceRatio) - 1;
257
+
258
+ return Math.abs(impermanentLoss) * 100;
259
+ }
260
+
261
+ /**
262
+ * Compare yield opportunities
263
+ */
264
+ export function rankYieldOpportunities(opportunities: YieldOpportunity[]): YieldOpportunity[] {
265
+ return opportunities.sort((a, b) => {
266
+ // Risk-adjusted APY calculation (simple risk penalty)
267
+ const riskPenalty = { low: 1, medium: 0.8, high: 0.6 };
268
+ const adjustedApyA = a.apy * riskPenalty[a.risk];
269
+ const adjustedApyB = b.apy * riskPenalty[b.risk];
270
+
271
+ return adjustedApyB - adjustedApyA;
272
+ });
273
+ }
274
+
275
+ // =============================================================================
276
+ // Utility Functions
277
+ // =============================================================================
278
+
279
+ /**
280
+ * Convert timeframe to milliseconds
281
+ */
282
+ export function timeFrameToMs(timeFrame: TimeFrame): number {
283
+ const units = {
284
+ second: 1000,
285
+ minute: 60 * 1000,
286
+ hour: 60 * 60 * 1000,
287
+ day: 24 * 60 * 60 * 1000,
288
+ week: 7 * 24 * 60 * 60 * 1000,
289
+ month: 30 * 24 * 60 * 60 * 1000
290
+ };
291
+
292
+ return timeFrame.value * units[timeFrame.unit];
293
+ }
294
+
295
+ /**
296
+ * Format currency with appropriate decimal places
297
+ */
298
+ export function formatCurrency(amount: number, decimals: number = 2): string {
299
+ return new Intl.NumberFormat('en-US', {
300
+ style: 'currency',
301
+ currency: 'USD',
302
+ minimumFractionDigits: decimals,
303
+ maximumFractionDigits: decimals
304
+ }).format(amount);
305
+ }
306
+
307
+ /**
308
+ * Format percentage with sign
309
+ */
310
+ export function formatPercentage(value: number, decimals: number = 2): string {
311
+ const sign = value >= 0 ? '+' : '';
312
+ return `${sign}${value.toFixed(decimals)}%`;
313
+ }
314
+
315
+ /**
316
+ * Check if price is within range
317
+ */
318
+ export function isPriceInRange(price: number, range: PriceRange): boolean {
319
+ return price >= range.min && price <= range.max;
320
+ }
321
+
322
+ /**
323
+ * Calculate distance from price to range bounds
324
+ */
325
+ export function distanceToRange(price: number, range: PriceRange): { toMin: number; toMax: number } {
326
+ return {
327
+ toMin: Math.abs(price - range.min),
328
+ toMax: Math.abs(price - range.max)
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Generate trading session ID
334
+ */
335
+ export function generateSessionId(): string {
336
+ const timestamp = Date.now().toString(36);
337
+ const random = Math.random().toString(36).substr(2, 5);
338
+ return `${timestamp}-${random}`;
339
+ }
340
+
341
+ /**
342
+ * Validate trading symbol format
343
+ */
344
+ export function isValidSymbol(symbol: string): boolean {
345
+ // Basic validation for crypto trading pairs
346
+ const regex = /^[A-Z]{2,10}[\/\-][A-Z]{2,10}$/;
347
+ return regex.test(symbol);
348
+ }
349
+
350
+ /**
351
+ * Normalize symbol format
352
+ */
353
+ export function normalizeSymbol(symbol: string): string {
354
+ return symbol.toUpperCase().replace('-', '/');
355
+ }
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Trading Validation Utilities for autonoma Agent Core
3
+ *
4
+ * Validation functions for trading data, orders, and parameters.
5
+ */
6
+
7
+ import { OrderRequest, Portfolio, RiskLimits } from './types.js';
8
+
9
+ // =============================================================================
10
+ // Order Validation
11
+ // =============================================================================
12
+
13
+ export interface ValidationResult {
14
+ isValid: boolean;
15
+ errors: string[];
16
+ warnings?: string[];
17
+ }
18
+
19
+ /**
20
+ * Validate order request
21
+ */
22
+ export function validateOrderRequest(orderRequest: OrderRequest): ValidationResult {
23
+ const errors: string[] = [];
24
+ const warnings: string[] = [];
25
+
26
+ // Basic required fields
27
+ if (!orderRequest.symbol) {
28
+ errors.push('Symbol is required');
29
+ }
30
+
31
+ if (!orderRequest.side || !['buy', 'sell'].includes(orderRequest.side)) {
32
+ errors.push('Valid side (buy/sell) is required');
33
+ }
34
+
35
+ if (!orderRequest.type || !['market', 'limit', 'stop', 'stop-limit'].includes(orderRequest.type)) {
36
+ errors.push('Valid order type is required');
37
+ }
38
+
39
+ if (!orderRequest.quantity || orderRequest.quantity <= 0) {
40
+ errors.push('Quantity must be positive');
41
+ }
42
+
43
+ // Price validation for limit orders
44
+ if (orderRequest.type === 'limit' && (!orderRequest.price || orderRequest.price <= 0)) {
45
+ errors.push('Price is required for limit orders');
46
+ }
47
+
48
+ // Stop price validation for stop orders
49
+ if (orderRequest.type === 'stop' && (!orderRequest.stopPrice || orderRequest.stopPrice <= 0)) {
50
+ errors.push('Stop price is required for stop orders');
51
+ }
52
+
53
+ // Symbol format validation
54
+ if (orderRequest.symbol && !isValidSymbolFormat(orderRequest.symbol)) {
55
+ errors.push('Invalid symbol format. Expected format: BASE/QUOTE');
56
+ }
57
+
58
+ // Warnings for potential issues
59
+ if (orderRequest.quantity && orderRequest.quantity > 1000000) {
60
+ warnings.push('Large order quantity may cause market impact');
61
+ }
62
+
63
+ return {
64
+ isValid: errors.length === 0,
65
+ errors,
66
+ warnings
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Validate symbol format
72
+ */
73
+ function isValidSymbolFormat(symbol: string): boolean {
74
+ const regex = /^[A-Z]{2,10}\/[A-Z]{2,10}$/;
75
+ return regex.test(symbol);
76
+ }
77
+
78
+ /**
79
+ * Validate order against risk limits
80
+ */
81
+ export function validateOrderAgainstRiskLimits(
82
+ orderRequest: OrderRequest,
83
+ portfolio: Portfolio,
84
+ riskLimits: RiskLimits
85
+ ): ValidationResult {
86
+ const errors: string[] = [];
87
+ const warnings: string[] = [];
88
+
89
+ // Check if symbol is blacklisted
90
+ if (riskLimits.blacklistedSymbols.includes(orderRequest.symbol)) {
91
+ errors.push(`Symbol ${orderRequest.symbol} is blacklisted`);
92
+ }
93
+
94
+ // Calculate order value
95
+ const orderValue = orderRequest.price ? orderRequest.price * orderRequest.quantity : 0;
96
+
97
+ // Check position size limit
98
+ if (orderValue > 0) {
99
+ const positionSizePercent = (orderValue / portfolio.totalValue) * 100;
100
+ if (positionSizePercent > riskLimits.maxPositionSize) {
101
+ errors.push(`Order would exceed maximum position size of ${riskLimits.maxPositionSize}%`);
102
+ }
103
+ }
104
+
105
+ // Check available cash for buy orders
106
+ if (orderRequest.side === 'buy' && orderValue > portfolio.cash) {
107
+ errors.push('Insufficient cash for buy order');
108
+ }
109
+
110
+ // Check position availability for sell orders
111
+ if (orderRequest.side === 'sell') {
112
+ const position = portfolio.positions.find(p => p.symbol === orderRequest.symbol);
113
+ if (!position || position.quantity < orderRequest.quantity) {
114
+ errors.push('Insufficient position for sell order');
115
+ }
116
+ }
117
+
118
+ return {
119
+ isValid: errors.length === 0,
120
+ errors,
121
+ warnings
122
+ };
123
+ }
124
+
125
+ // =============================================================================
126
+ // Portfolio Validation
127
+ // =============================================================================
128
+
129
+ /**
130
+ * Validate portfolio consistency
131
+ */
132
+ export function validatePortfolio(portfolio: Portfolio): ValidationResult {
133
+ const errors: string[] = [];
134
+ const warnings: string[] = [];
135
+
136
+ // Check for negative cash
137
+ if (portfolio.cash < 0) {
138
+ errors.push('Portfolio cash cannot be negative');
139
+ }
140
+
141
+ // Check for negative positions
142
+ portfolio.positions.forEach((position, index) => {
143
+ if (position.quantity < 0) {
144
+ errors.push(`Position ${index} has negative quantity`);
145
+ }
146
+
147
+ if (position.averagePrice <= 0) {
148
+ errors.push(`Position ${index} has invalid average price`);
149
+ }
150
+ });
151
+
152
+ // Check for duplicate positions
153
+ const symbols = portfolio.positions.map(p => p.symbol);
154
+ const duplicates = symbols.filter((symbol, index) => symbols.indexOf(symbol) !== index);
155
+ if (duplicates.length > 0) {
156
+ errors.push(`Duplicate positions found: ${duplicates.join(', ')}`);
157
+ }
158
+
159
+ // Warnings for portfolio health
160
+ const totalValue = portfolio.totalValue;
161
+ if (totalValue > 0) {
162
+ const cashPercent = (portfolio.cash / totalValue) * 100;
163
+ if (cashPercent > 90) {
164
+ warnings.push('Portfolio is highly concentrated in cash');
165
+ } else if (cashPercent < 5) {
166
+ warnings.push('Portfolio has very low cash reserves');
167
+ }
168
+ }
169
+
170
+ return {
171
+ isValid: errors.length === 0,
172
+ errors,
173
+ warnings
174
+ };
175
+ }
176
+
177
+ // =============================================================================
178
+ // Risk Validation
179
+ // =============================================================================
180
+
181
+ /**
182
+ * Validate risk limits configuration
183
+ */
184
+ export function validateRiskLimits(riskLimits: RiskLimits): ValidationResult {
185
+ const errors: string[] = [];
186
+ const warnings: string[] = [];
187
+
188
+ // Check percentage limits
189
+ if (riskLimits.maxPositionSize <= 0 || riskLimits.maxPositionSize > 100) {
190
+ errors.push('Max position size must be between 0 and 100 percent');
191
+ }
192
+
193
+ if (riskLimits.maxDrawdown <= 0 || riskLimits.maxDrawdown > 100) {
194
+ errors.push('Max drawdown must be between 0 and 100 percent');
195
+ }
196
+
197
+ // Check dollar limits
198
+ if (riskLimits.maxDailyLoss <= 0) {
199
+ errors.push('Max daily loss must be positive');
200
+ }
201
+
202
+ // Check leverage
203
+ if (riskLimits.maxLeverage <= 0) {
204
+ errors.push('Max leverage must be positive');
205
+ }
206
+
207
+ // Warnings for conservative limits
208
+ if (riskLimits.maxPositionSize > 20) {
209
+ warnings.push('Position size limit above 20% may be risky');
210
+ }
211
+
212
+ if (riskLimits.maxLeverage > 10) {
213
+ warnings.push('High leverage increases risk significantly');
214
+ }
215
+
216
+ return {
217
+ isValid: errors.length === 0,
218
+ errors,
219
+ warnings
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Check if portfolio violates risk limits
225
+ */
226
+ export function checkRiskLimitViolations(
227
+ portfolio: Portfolio,
228
+ riskLimits: RiskLimits,
229
+ dailyPnL: number
230
+ ): ValidationResult {
231
+ const errors: string[] = [];
232
+ const warnings: string[] = [];
233
+
234
+ // Check daily loss limit
235
+ if (dailyPnL < 0 && Math.abs(dailyPnL) > riskLimits.maxDailyLoss) {
236
+ errors.push(`Daily loss of $${Math.abs(dailyPnL)} exceeds limit of $${riskLimits.maxDailyLoss}`);
237
+ }
238
+
239
+ // Check position concentration
240
+ portfolio.positions.forEach(position => {
241
+ const positionPercent = (position.marketValue / portfolio.totalValue) * 100;
242
+ if (positionPercent > riskLimits.maxPositionSize) {
243
+ errors.push(`Position in ${position.symbol} (${positionPercent.toFixed(1)}%) exceeds limit of ${riskLimits.maxPositionSize}%`);
244
+ }
245
+ });
246
+
247
+ // Check for blacklisted symbols
248
+ const blacklistedPositions = portfolio.positions.filter(p =>
249
+ riskLimits.blacklistedSymbols.includes(p.symbol)
250
+ );
251
+ if (blacklistedPositions.length > 0) {
252
+ errors.push(`Positions in blacklisted symbols: ${blacklistedPositions.map(p => p.symbol).join(', ')}`);
253
+ }
254
+
255
+ return {
256
+ isValid: errors.length === 0,
257
+ errors,
258
+ warnings
259
+ };
260
+ }
261
+
262
+ // =============================================================================
263
+ // Price Validation
264
+ // =============================================================================
265
+
266
+ /**
267
+ * Validate price data
268
+ */
269
+ export function validatePriceData(prices: number[]): ValidationResult {
270
+ const errors: string[] = [];
271
+ const warnings: string[] = [];
272
+
273
+ if (prices.length === 0) {
274
+ errors.push('Price data is empty');
275
+ return { isValid: false, errors, warnings };
276
+ }
277
+
278
+ // Check for negative prices
279
+ const negativeCount = prices.filter(price => price <= 0).length;
280
+ if (negativeCount > 0) {
281
+ errors.push(`Found ${negativeCount} non-positive prices`);
282
+ }
283
+
284
+ // Check for extreme outliers
285
+ const mean = prices.reduce((sum, price) => sum + price, 0) / prices.length;
286
+ const outliers = prices.filter(price => Math.abs(price - mean) > mean * 2);
287
+ if (outliers.length > 0) {
288
+ warnings.push(`Found ${outliers.length} potential price outliers`);
289
+ }
290
+
291
+ return {
292
+ isValid: errors.length === 0,
293
+ errors,
294
+ warnings
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Validate price is within reasonable bounds
300
+ */
301
+ export function validatePriceRange(
302
+ price: number,
303
+ referencePrice: number,
304
+ maxDeviationPercent: number = 10
305
+ ): ValidationResult {
306
+ const errors: string[] = [];
307
+
308
+ if (price <= 0) {
309
+ errors.push('Price must be positive');
310
+ }
311
+
312
+ const deviation = Math.abs((price - referencePrice) / referencePrice) * 100;
313
+ if (deviation > maxDeviationPercent) {
314
+ errors.push(`Price deviation of ${deviation.toFixed(1)}% exceeds maximum of ${maxDeviationPercent}%`);
315
+ }
316
+
317
+ return {
318
+ isValid: errors.length === 0,
319
+ errors
320
+ };
321
+ }