diff --git a/apps/stock/data-ingestion/node_modules b/apps/stock/data-ingestion/node_modules deleted file mode 120000 index 6c57164..0000000 --- a/apps/stock/data-ingestion/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../node_modules \ No newline at end of file diff --git a/apps/stock/data-ingestion/src/handlers/eod/actions/corporate-actions.ts b/apps/stock/data-ingestion/src/handlers/eod/actions/corporate-actions.ts index 89a2b88..bd9741a 100644 --- a/apps/stock/data-ingestion/src/handlers/eod/actions/corporate-actions.ts +++ b/apps/stock/data-ingestion/src/handlers/eod/actions/corporate-actions.ts @@ -130,11 +130,9 @@ export async function fetchCorporateActions( } // Build URL based on action type - // Use utility function to handle US symbols and EUFUND special case - const exchangeSuffix = getEodExchangeSuffix(exchange, country); - + // eodSearchCode already contains the symbol with exchange suffix (e.g., AAPL.US) const endpoint = actionType === 'dividends' ? 'div' : 'splits'; - const url = new URL(`https://eodhd.com/api/${endpoint}/${symbol}.${exchangeSuffix}`); + const url = new URL(`https://eodhd.com/api/${endpoint}/${eodSearchCode}`); url.searchParams.append('api_token', apiKey); url.searchParams.append('fmt', 'json'); diff --git a/apps/stock/data-ingestion/src/handlers/eod/actions/fundamentals.ts b/apps/stock/data-ingestion/src/handlers/eod/actions/fundamentals.ts index eb87787..f8b1ebb 100644 --- a/apps/stock/data-ingestion/src/handlers/eod/actions/fundamentals.ts +++ b/apps/stock/data-ingestion/src/handlers/eod/actions/fundamentals.ts @@ -139,18 +139,17 @@ export async function fetchBulkFundamentals( throw new Error('EOD API key not configured'); } - // Group symbols by actual exchange for API endpoint, but use country for symbol suffix + // Group symbols by actual exchange for API endpoint + // eodSearchCode already contains the symbol with exchange suffix (e.g., AAPL.US) const exchangeGroups = symbolDocs.reduce((acc, symbolDoc) => { - const symbol = symbolDoc.Code; const exchange = symbolDoc.eodExchange || symbolDoc.Exchange; - const country = symbolDoc.Country; + const eodSearchCode = symbolDoc.eodSearchCode; if (!acc[exchange]) { acc[exchange] = []; } - // Use utility function to handle US symbols and EUFUND special case - const exchangeSuffix = getEodExchangeSuffix(exchange, country); - acc[exchange].push(`${symbol}.${exchangeSuffix}`); + // eodSearchCode already has the correct format (e.g., AAPL.US) + acc[exchange].push(eodSearchCode); return acc; }, {} as Record); @@ -309,10 +308,8 @@ export async function fetchSingleFundamentals( } // Build URL for single fundamentals endpoint - // Use utility function to handle US symbols and EUFUND special case - const exchangeSuffix = getEodExchangeSuffix(exchange, country); - - const url = new URL(`https://eodhd.com/api/fundamentals/${symbol}.${exchangeSuffix}`); + // eodSearchCode already contains the symbol with exchange suffix (e.g., AAPL.US) + const url = new URL(`https://eodhd.com/api/fundamentals/${eodSearchCode}`); url.searchParams.append('api_token', apiKey); url.searchParams.append('fmt', 'json'); diff --git a/apps/stock/data-ingestion/src/handlers/eod/actions/intraday.ts b/apps/stock/data-ingestion/src/handlers/eod/actions/intraday.ts index 8035212..126876c 100644 --- a/apps/stock/data-ingestion/src/handlers/eod/actions/intraday.ts +++ b/apps/stock/data-ingestion/src/handlers/eod/actions/intraday.ts @@ -303,7 +303,7 @@ export async function crawlIntraday( finished: updateData.finished }; } catch (error) { - logger.error('Failed to crawl intraday data', { error, symbol, exchange, interval }); + logger.error('Failed to crawl intraday data', { error, eodSearchCode, interval }); throw error; } } diff --git a/apps/stock/data-ingestion/src/handlers/eod/eod.handler.ts b/apps/stock/data-ingestion/src/handlers/eod/eod.handler.ts index 8733da0..becf0da 100644 --- a/apps/stock/data-ingestion/src/handlers/eod/eod.handler.ts +++ b/apps/stock/data-ingestion/src/handlers/eod/eod.handler.ts @@ -39,7 +39,7 @@ import { createEODOperationRegistry } from './shared'; ], }) export class EodHandler extends BaseHandler { - public operationRegistry: OperationRegistry; + public operationRegistry!: OperationRegistry; constructor(services: any) { super(services); diff --git a/apps/stock/data-ingestion/src/handlers/qm/qm.handler.ts b/apps/stock/data-ingestion/src/handlers/qm/qm.handler.ts index 30e0956..9c57b54 100644 --- a/apps/stock/data-ingestion/src/handlers/qm/qm.handler.ts +++ b/apps/stock/data-ingestion/src/handlers/qm/qm.handler.ts @@ -35,7 +35,7 @@ import { createQMOperationRegistry } from './shared/operation-provider'; @Handler('qm') @Disabled() // Disable by default, enable specific operations as needed export class QMHandler extends BaseHandler { - public operationRegistry: OperationRegistry; + public operationRegistry!: OperationRegistry; constructor(services: any) { super(services); // Handler name read from @Handler decorator diff --git a/apps/stock/data-ingestion/src/shared/operation-manager/OperationRegistry.ts b/apps/stock/data-ingestion/src/shared/operation-manager/OperationRegistry.ts index 221628b..41372fd 100644 --- a/apps/stock/data-ingestion/src/shared/operation-manager/OperationRegistry.ts +++ b/apps/stock/data-ingestion/src/shared/operation-manager/OperationRegistry.ts @@ -7,11 +7,10 @@ import { BaseOperationProvider } from './BaseOperationProvider'; import { OperationTracker } from './OperationTracker'; import type { OperationComponentOptions, - OperationUpdate, - StaleSymbolOptions, - BulkOperationUpdate, + OperationConfig, OperationStats, - OperationConfig + OperationUpdate, + StaleSymbolOptions } from './types'; /** diff --git a/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts b/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts index 4a016c7..d2456f2 100644 --- a/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts +++ b/apps/stock/orchestrator/src/analytics/PerformanceAnalyzer.ts @@ -161,7 +161,20 @@ export class PerformanceAnalyzer { sortinoRatio, calmarRatio, informationRatio, - ...tradeStats, + totalTrades: tradeStats.totalTrades ?? 0, + winRate: tradeStats.winRate ?? 0, + avgWin: tradeStats.avgWin ?? 0, + avgLoss: tradeStats.avgLoss ?? 0, + avgWinLoss: tradeStats.avgWinLoss ?? 0, + profitFactor: tradeStats.profitFactor ?? 0, + expectancy: tradeStats.expectancy ?? 0, + payoffRatio: tradeStats.payoffRatio ?? 0, + avgHoldingPeriod: tradeStats.avgHoldingPeriod ?? 0, + avgTradesPerDay: tradeStats.avgTradesPerDay ?? 0, + maxConsecutiveWins: tradeStats.maxConsecutiveWins ?? 0, + maxConsecutiveLosses: tradeStats.maxConsecutiveLosses ?? 0, + largestWin: tradeStats.largestWin ?? 0, + largestLoss: tradeStats.largestLoss ?? 0, skewness, kurtosis, tailRatio, @@ -178,9 +191,8 @@ export class PerformanceAnalyzer { if (this.equityCurve.length === 0) { return { maxDrawdown: 0, - averageDrawdown: 0, maxDrawdownDuration: 0, - underwaterTime: 0, + underwaterCurve: [], drawdownPeriods: [], currentDrawdown: 0 }; diff --git a/apps/stock/orchestrator/src/backtest/BacktestEngine.ts b/apps/stock/orchestrator/src/backtest/BacktestEngine.ts index 9c6d10d..8a478b9 100644 --- a/apps/stock/orchestrator/src/backtest/BacktestEngine.ts +++ b/apps/stock/orchestrator/src/backtest/BacktestEngine.ts @@ -98,7 +98,7 @@ export class BacktestEngine extends EventEmitter { private lastProcessTime = 0; private dataManager: DataManager; private marketSimulator: MarketSimulator; - private performanceAnalyzer: PerformanceAnalyzer; + private performanceAnalyzer!: PerformanceAnalyzer; private microstructures: Map = new Map(); private container: IServiceContainer; private runId: string | null = null; diff --git a/apps/stock/web-app/src/components/ErrorBoundary.tsx b/apps/stock/web-app/src/components/ErrorBoundary.tsx index 90e0ccb..fd7a921 100644 --- a/apps/stock/web-app/src/components/ErrorBoundary.tsx +++ b/apps/stock/web-app/src/components/ErrorBoundary.tsx @@ -1,4 +1,4 @@ -import React, { Component, ReactNode } from 'react'; +import React, { Component, type ReactNode } from 'react'; interface Props { children: ReactNode; @@ -20,11 +20,11 @@ export class ErrorBoundary extends Component { return { hasError: true, error }; } - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + override componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('ErrorBoundary caught an error:', error, errorInfo); } - render() { + override render() { if (this.state.hasError) { return ( this.props.fallback || ( diff --git a/apps/stock/web-app/src/components/charts/Chart.tsx b/apps/stock/web-app/src/components/charts/Chart.tsx index 93bf99f..27a8edf 100644 --- a/apps/stock/web-app/src/components/charts/Chart.tsx +++ b/apps/stock/web-app/src/components/charts/Chart.tsx @@ -58,7 +58,6 @@ export function Chart({ return `${chartId}-${Date.now()}`; }, [chartId]) const mountedRef = useRef(true); - const lastChartIdRef = useRef(chartId); // Track component mount state useEffect(() => { @@ -126,13 +125,13 @@ export function Chart({ // Use requestAnimationFrame to ensure DOM is ready and avoid conflicts const rafId = requestAnimationFrame(() => { - const initTimeout = setTimeout(() => { + setTimeout(() => { if (!chartContainerRef.current || isCleanedUp || !mountedRef.current) return; // Create chart using the chart manager const chart = createChart(uniqueChartId, chartContainerRef.current, { width: chartContainerRef.current.clientWidth, - height: height, + height: typeof height === 'string' ? parseInt(height) : height, layout: { background: { type: LightweightCharts.ColorType.Solid, @@ -188,7 +187,7 @@ export function Chart({ time: timeInSeconds as LightweightCharts.Time }; }) - .filter((item, index) => { + .filter((item) => { if (seen.has(item.time)) { return false; } @@ -201,7 +200,7 @@ export function Chart({ // Create main series let mainSeries: LightweightCharts.ISeriesApi | null = null; - if (type === 'candlestick' && data[0].open !== undefined) { + if (type === 'candlestick' && data[0]?.open !== undefined) { mainSeries = chart.addCandlestickSeries({ upColor: '#10b981', downColor: '#ef4444', @@ -212,7 +211,7 @@ export function Chart({ }); const validData = validateAndFilterData(data); mainSeries.setData(validData as LightweightCharts.CandlestickData[]); - } else if (type === 'line' || (type === 'candlestick' && data[0].value !== undefined)) { + } else if (type === 'line' || (type === 'candlestick' && data[0]?.value !== undefined)) { mainSeries = chart.addLineSeries({ color: '#3b82f6', lineWidth: 2, @@ -278,7 +277,7 @@ export function Chart({ overlayData.forEach((overlay, index) => { const series = chart.addLineSeries({ color: overlay.color || ['#ff9800', '#4caf50', '#9c27b0', '#f44336'][index % 4], - lineWidth: overlay.lineWidth || 2, + lineWidth: (overlay.lineWidth || 2) as LightweightCharts.LineWidth, title: overlay.name, priceScaleId: index === 0 ? '' : `overlay-${index}`, // First overlay uses main scale }); @@ -371,7 +370,7 @@ export function Chart({ }); } catch (error) { // Ignore errors if the chart is being disposed - if (!error?.message?.includes('disposed')) { + if (!(error instanceof Error) || !error.message?.includes('disposed')) { console.error('Error resizing chart:', error); } } diff --git a/apps/stock/web-app/src/components/ui/DataTable/types.ts b/apps/stock/web-app/src/components/ui/DataTable/types.ts index 2647a10..98e0204 100644 --- a/apps/stock/web-app/src/components/ui/DataTable/types.ts +++ b/apps/stock/web-app/src/components/ui/DataTable/types.ts @@ -1,4 +1,4 @@ -import { ReactNode } from 'react'; +import type { ReactNode } from 'react'; export interface TableColumn> { id: string; diff --git a/apps/stock/web-app/src/features/backtest/BacktestDetailPageV2.tsx b/apps/stock/web-app/src/features/backtest/BacktestDetailPageV2.tsx index f35fbf6..23ef727 100644 --- a/apps/stock/web-app/src/features/backtest/BacktestDetailPageV2.tsx +++ b/apps/stock/web-app/src/features/backtest/BacktestDetailPageV2.tsx @@ -38,13 +38,12 @@ export function BacktestDetailPageV2() { createRun, pauseRun, resumeRun, - cancelRun, updateRunSpeed, selectRun, } = useBacktestV2(); // WebSocket connection for real-time updates - const { isConnected } = useWebSocket({ + useWebSocket({ runId: currentRun?.id || null, onProgress: (progress, currentDate) => { // Update the run progress in the UI @@ -60,7 +59,7 @@ export function BacktestDetailPageV2() { loadBacktest(id); } }, - onCompleted: async (results) => { + onCompleted: async () => { // When run completes, reload to get the final results if (currentRun?.id) { try { diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx index 5c6c22a..6f420ea 100644 --- a/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx +++ b/apps/stock/web-app/src/features/backtest/components/BacktestChart.tsx @@ -3,8 +3,36 @@ import { useState, useMemo, memo } from 'react'; import { Chart } from '../../../components/charts/Chart'; import { ChartContainer } from './ChartContainer'; +interface ExtendedBacktestResult extends BacktestResult { + equity?: Array<{ date: string; value: number }>; + ohlcData?: Record>; + runId?: string; +} + +interface ExtendedTrade { + id: string; + timestamp: string; + symbol: string; + side: 'buy' | 'sell'; + quantity: number; + price: number; + commission: number; + pnl?: number; + positionAfter: number; + entryPrice?: number; + entryDate?: string; + date?: string; +} + interface BacktestChartProps { - result: BacktestResult | null; + result: ExtendedBacktestResult | null; isLoading: boolean; } @@ -13,36 +41,43 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }: const [selectedSymbol, setSelectedSymbol] = useState(''); const chartData = useMemo(() => { - if (!result?.equity || !result?.ohlcData) return null; + if (!result || (!result.equity && !result.performanceData) || (!result.ohlcData && !result.trades)) return null; - const symbols = Object.keys(result.ohlcData); + const symbols = result.ohlcData ? Object.keys(result.ohlcData) : (result.trades ? [...new Set(result.trades.map(t => t.symbol))] : []); const symbol = selectedSymbol || symbols[0] || ''; - const ohlcData = result.ohlcData[symbol] || []; + const ohlcData = result.ohlcData?.[symbol] || []; // Remove excessive logging in production // Log only on significant changes if (process.env.NODE_ENV === 'development' && ohlcData.length > 0) { // Use a simple hash to detect actual data changes - const dataHash = `${symbols.length}-${result.equity?.length}-${ohlcData.length}`; + const dataHash = `${symbols.length}-${result.equity?.length || result.performanceData?.length}-${ohlcData.length}`; if ((window as any).__lastDataHash !== dataHash) { (window as any).__lastDataHash = dataHash; console.log('BacktestChart data updated:', { symbols, selectedSymbol, symbol, - ohlcDataKeys: Object.keys(result.ohlcData), - equityLength: result.equity?.length, + ohlcDataKeys: result.ohlcData ? Object.keys(result.ohlcData) : [], + equityLength: result.equity?.length || result.performanceData?.length, tradesLength: result.trades?.length }); } } - const equityData = (result.equity || []) - .filter(e => e && e.date && e.value != null) - .map(e => ({ - time: new Date(e.date).getTime() / 1000, - value: e.value - })); + const equityData = result.equity + ? result.equity + .filter(e => e && e.date && e.value != null) + .map(e => ({ + time: new Date(e.date).getTime() / 1000, + value: e.value + })) + : result.performanceData + ?.filter(p => p && p.timestamp && p.portfolioValue != null) + .map(p => ({ + time: new Date(p.timestamp).getTime() / 1000, + value: p.portfolioValue + })) || []; // Find trades for this symbol const symbolTrades = result.trades?.filter(t => t.symbol === symbol) || []; @@ -51,15 +86,17 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }: const tradeMarkers = symbolTrades .filter(trade => { // Check multiple possible field names - const hasPrice = trade.price != null || trade.entryPrice != null; - const hasTime = trade.timestamp != null || trade.entryDate != null || trade.date != null; + const extTrade = trade as ExtendedTrade; + const hasPrice = extTrade.price != null || extTrade.entryPrice != null; + const hasTime = extTrade.timestamp != null || extTrade.entryDate != null || extTrade.date != null; return hasPrice && hasTime; }) .map(trade => { // Use whatever field names are present - const price = trade.price || trade.entryPrice; - const timestamp = trade.timestamp || trade.entryDate || trade.date; - const side = trade.side || (trade.quantity > 0 ? 'buy' : 'sell'); + const extTrade = trade as ExtendedTrade; + const price = extTrade.price || extTrade.entryPrice || 0; + const timestamp = extTrade.timestamp || extTrade.entryDate || extTrade.date || ''; + const side = extTrade.side || (extTrade.quantity > 0 ? 'buy' : 'sell'); return { time: new Date(timestamp).getTime() / 1000, @@ -140,8 +177,8 @@ export const BacktestChart = memo(function BacktestChart({ result, isLoading }: ]} tradeMarkers={chartData.tradeMarkers} height={height} - chartId={`backtest-${result?.runId || 'default'}`} - key={result?.runId || 'default'} + chartId={`backtest-${result?.runId || result?.id || 'default'}`} + key={result?.runId || result?.id || 'default'} /> )} diff --git a/apps/stock/web-app/src/features/backtest/components/BacktestMetrics.tsx b/apps/stock/web-app/src/features/backtest/components/BacktestMetrics.tsx index c6d8684..cb66c12 100644 --- a/apps/stock/web-app/src/features/backtest/components/BacktestMetrics.tsx +++ b/apps/stock/web-app/src/features/backtest/components/BacktestMetrics.tsx @@ -37,22 +37,22 @@ export function BacktestMetrics({ result, isLoading }: BacktestMetricsProps) { = 0 ? 'success' : 'error'} + trend={(metrics.totalReturn ?? 0) >= 0 ? 'up' : 'down'} /> 1 ? 'success' : (metrics.sharpeRatio ?? 0) > 0 ? 'warning' : 'error'} + trend={(metrics.sharpeRatio ?? 0) > 0 ? 'up' : 'down'} /> -0.2 ? 'warning' : 'error'} + trend='down' /> 0.5 ? 'success' : 'warning'} + trend={(metrics.winRate ?? 0) > 0.5 ? 'up' : 'down'} /> 1.5 ? 'success' : (metrics.profitFactor ?? 0) > 1 ? 'warning' : 'error'} + trend={(metrics.profitFactor ?? 0) > 1 ? 'up' : 'down'} /> diff --git a/apps/stock/web-app/src/features/charts/utils/indicators.ts b/apps/stock/web-app/src/features/charts/utils/indicators.ts index bf16f92..71f60ea 100644 --- a/apps/stock/web-app/src/features/charts/utils/indicators.ts +++ b/apps/stock/web-app/src/features/charts/utils/indicators.ts @@ -7,12 +7,16 @@ export function calculateSMA(data: CandlestickData[], period: number): LineData[ for (let i = period - 1; i < data.length; i++) { let sum = 0; for (let j = 0; j < period; j++) { - sum += data[i - j].close; + const item = data[i - j]; + if (item) sum += item.close; + } + const current = data[i]; + if (current) { + result.push({ + time: current.time, + value: parseFloat((sum / period).toFixed(2)), + }); } - result.push({ - time: data[i].time, - value: parseFloat((sum / period).toFixed(2)), - }); } return result; @@ -26,22 +30,29 @@ export function calculateEMA(data: CandlestickData[], period: number): LineData[ // Start with SMA for the first period let sum = 0; for (let i = 0; i < period; i++) { - sum += data[i].close; + const item = data[i]; + if (item) sum += item.close; } let ema = sum / period; - result.push({ - time: data[period - 1].time, - value: parseFloat(ema.toFixed(2)), - }); + const firstItem = data[period - 1]; + if (firstItem) { + result.push({ + time: firstItem.time, + value: parseFloat(ema.toFixed(2)), + }); + } // Calculate EMA for the rest for (let i = period; i < data.length; i++) { - ema = (data[i].close - ema) * multiplier + ema; - result.push({ - time: data[i].time, - value: parseFloat(ema.toFixed(2)), - }); + const current = data[i]; + if (current) { + ema = (current.close - ema) * multiplier + ema; + result.push({ + time: current.time, + value: parseFloat(ema.toFixed(2)), + }); + } } return result; @@ -55,26 +66,34 @@ export function calculateBollingerBands(data: CandlestickData[], period: number for (let i = period - 1; i < data.length; i++) { let sumSquaredDiff = 0; - const smaValue = sma[i - (period - 1)].value; + const smaItem = sma[i - (period - 1)]; + if (!smaItem) continue; + const smaValue = smaItem.value; for (let j = 0; j < period; j++) { - const diff = data[i - j].close - smaValue; - sumSquaredDiff += diff * diff; + const item = data[i - j]; + if (item) { + const diff = item.close - smaValue; + sumSquaredDiff += diff * diff; + } } const standardDeviation = Math.sqrt(sumSquaredDiff / period); const upperBand = smaValue + (standardDeviation * stdDev); const lowerBand = smaValue - (standardDeviation * stdDev); - upper.push({ - time: data[i].time, - value: parseFloat(upperBand.toFixed(2)), - }); - - lower.push({ - time: data[i].time, - value: parseFloat(lowerBand.toFixed(2)), - }); + const current = data[i]; + if (current) { + upper.push({ + time: current.time, + value: parseFloat(upperBand.toFixed(2)), + }); + + lower.push({ + time: current.time, + value: parseFloat(lowerBand.toFixed(2)), + }); + } } return { upper, middle: sma, lower }; @@ -91,11 +110,15 @@ export function calculateRSI(data: CandlestickData[], period: number = 14): Line let avgLoss = 0; for (let i = 1; i <= period; i++) { - const change = data[i].close - data[i - 1].close; - if (change > 0) { - avgGain += change; - } else { - avgLoss += Math.abs(change); + const current = data[i]; + const prev = data[i - 1]; + if (current && prev) { + const change = current.close - prev.close; + if (change > 0) { + avgGain += change; + } else { + avgLoss += Math.abs(change); + } } } @@ -104,7 +127,10 @@ export function calculateRSI(data: CandlestickData[], period: number = 14): Line // Calculate RSI for each period for (let i = period; i < data.length; i++) { - const change = data[i].close - data[i - 1].close; + const current = data[i]; + const prev = data[i - 1]; + if (!current || !prev) continue; + const change = current.close - prev.close; const gain = change > 0 ? change : 0; const loss = change < 0 ? Math.abs(change) : 0; @@ -115,7 +141,7 @@ export function calculateRSI(data: CandlestickData[], period: number = 14): Line const rsi = 100 - (100 / (1 + rs)); result.push({ - time: data[i].time, + time: current.time, value: parseFloat(rsi.toFixed(2)), }); } @@ -155,17 +181,23 @@ export function calculateMACD( const multiplier = 2 / (signalPeriod + 1); let ema = macdData.slice(0, signalPeriod).reduce((a, b) => a + b, 0) / signalPeriod; - signalLine.push({ - time: macdLine[signalPeriod - 1].time, - value: parseFloat(ema.toFixed(2)), - }); + const firstMacd = macdLine[signalPeriod - 1]; + if (firstMacd) { + signalLine.push({ + time: firstMacd.time, + value: parseFloat(ema.toFixed(2)), + }); + } for (let i = signalPeriod; i < macdData.length; i++) { ema = (macdData[i] - ema) * multiplier + ema; - signalLine.push({ - time: macdLine[i].time, - value: parseFloat(ema.toFixed(2)), - }); + const macdItem = macdLine[i]; + if (macdItem) { + signalLine.push({ + time: macdItem.time, + value: parseFloat(ema.toFixed(2)), + }); + } } } @@ -191,15 +223,17 @@ export function calculateVWAP(data: CandlestickData[]): LineData[] { let cumulativeVolume = 0; for (let i = 0; i < data.length; i++) { - const typicalPrice = (data[i].high + data[i].low + data[i].close) / 3; - const volume = data[i].volume || 0; + const current = data[i]; + if (!current) continue; + const typicalPrice = (current.high + current.low + current.close) / 3; + const volume = current.volume || 0; cumulativeTPV += typicalPrice * volume; cumulativeVolume += volume; if (cumulativeVolume > 0) { result.push({ - time: data[i].time, + time: current.time, value: parseFloat((cumulativeTPV / cumulativeVolume).toFixed(2)), }); } @@ -211,21 +245,24 @@ export function calculateVWAP(data: CandlestickData[]): LineData[] { // Stochastic Oscillator export function calculateStochastic(data: CandlestickData[], period: number = 14, smoothK: number = 3, smoothD: number = 3) { const kValues: LineData[] = []; - const dValues: LineData[] = []; // Calculate %K for (let i = period - 1; i < data.length; i++) { - let lowestLow = data[i].low; - let highestHigh = data[i].high; + const current = data[i]; + if (!current) continue; + let lowestLow = current.low; + let highestHigh = current.high; for (let j = 1; j < period; j++) { - lowestLow = Math.min(lowestLow, data[i - j].low); - highestHigh = Math.max(highestHigh, data[i - j].high); + const prev = data[i - j]; + if (!prev) continue; + lowestLow = Math.min(lowestLow, prev.low); + highestHigh = Math.max(highestHigh, prev.high); } - const k = ((data[i].close - lowestLow) / (highestHigh - lowestLow)) * 100; + const k = ((current.close - lowestLow) / (highestHigh - lowestLow)) * 100; kValues.push({ - time: data[i].time, + time: current.time, value: parseFloat(k.toFixed(2)), }); } diff --git a/apps/stock/web-app/src/features/dashboard/components/PortfolioTable.tsx b/apps/stock/web-app/src/features/dashboard/components/PortfolioTable.tsx index b31f574..daeb6aa 100644 --- a/apps/stock/web-app/src/features/dashboard/components/PortfolioTable.tsx +++ b/apps/stock/web-app/src/features/dashboard/components/PortfolioTable.tsx @@ -162,22 +162,22 @@ export function PortfolioTable() { sharpe: Math.random() * 3, alpha: (Math.random() - 0.5) * 20, correlation: (Math.random() - 0.5) * 2, - sector: sectors[Math.floor(Math.random() * sectors.length)], - industry: industries[Math.floor(Math.random() * industries.length)], - country: countries[Math.floor(Math.random() * countries.length)], - exchange: exchanges[Math.floor(Math.random() * exchanges.length)], - currency: currencies[Math.floor(Math.random() * currencies.length)], + sector: sectors[Math.floor(Math.random() * sectors.length)] || 'Technology', + industry: industries[Math.floor(Math.random() * industries.length)] || 'Software', + country: countries[Math.floor(Math.random() * countries.length)] || 'USA', + exchange: exchanges[Math.floor(Math.random() * exchanges.length)] || 'NYSE', + currency: currencies[Math.floor(Math.random() * currencies.length)] || 'USD', lastUpdate: new Date(Date.now() - Math.random() * 86400000).toISOString(), - analyst1: analysts[Math.floor(Math.random() * analysts.length)], - analyst2: analysts[Math.floor(Math.random() * analysts.length)], - analyst3: analysts[Math.floor(Math.random() * analysts.length)], + analyst1: analysts[Math.floor(Math.random() * analysts.length)] || 'Goldman Sachs', + analyst2: analysts[Math.floor(Math.random() * analysts.length)] || 'Morgan Stanley', + analyst3: analysts[Math.floor(Math.random() * analysts.length)] || 'JPMorgan', rating1: Math.random() * 5 + 1, rating2: Math.random() * 5 + 1, rating3: Math.random() * 5 + 1, target1: basePrice + (Math.random() - 0.3) * 50, target2: basePrice + (Math.random() - 0.3) * 50, target3: basePrice + (Math.random() - 0.3) * 50, - risk: risks[Math.floor(Math.random() * risks.length)], + risk: risks[Math.floor(Math.random() * risks.length)] || 'Medium', esg: Math.random() * 100, }; }); diff --git a/apps/stock/web-app/src/features/exchanges/ExchangesPage.tsx b/apps/stock/web-app/src/features/exchanges/ExchangesPage.tsx index 447cec5..e47f7ce 100644 --- a/apps/stock/web-app/src/features/exchanges/ExchangesPage.tsx +++ b/apps/stock/web-app/src/features/exchanges/ExchangesPage.tsx @@ -27,6 +27,7 @@ export function ExchangesPage() { }, 5000); return () => clearTimeout(timer); } + return undefined; }, [syncStatus.type]); const handleSync = async () => { diff --git a/libs/core/di/src/index.ts b/libs/core/di/src/index.ts index 062e9c5..9bc147d 100644 --- a/libs/core/di/src/index.ts +++ b/libs/core/di/src/index.ts @@ -5,6 +5,7 @@ export { OperationContext } from './operation-context'; export { calculatePoolSize, getServicePoolSize, getHandlerPoolSize } from './pool-size-calculator'; export { ServiceLifecycleManager } from './utils/lifecycle'; export { HandlerScanner } from './scanner/handler-scanner'; +export type { IServiceContainer } from '@stock-bot/types'; // Export schemas export {