import type { ChatMode } from '@/types/chat' const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' const API_ENDPOINT = import.meta.env.VITE_API_ENDPOINT || '/chat/stream' const USE_MOCK_DATA = false // Set to true to use mock data for testing // Session management export const getChatSessionId = (): string | null => { return localStorage.getItem('tyndale_session_id') } export const setChatSessionId = (sessionId: string): void => { localStorage.setItem('tyndale_session_id', sessionId) } export const clearChatSessionId = (): void => { localStorage.removeItem('tyndale_session_id') } // Mock responses for testing const MOCK_RESPONSES = { general: [ "A **moving average** is one of the most fundamental technical indicators in trading. It smooths out price data by creating a constantly updated average price over a specific time period.\n\nThere are two main types:\n- **Simple Moving Average (SMA)**: The arithmetic mean of prices over a period\n- **Exponential Moving Average (EMA)**: Gives more weight to recent prices\n\nTraders use moving averages to identify trends, support/resistance levels, and generate trading signals when different MAs cross each other.", "**Algorithmic trading** uses computer programs to execute trades based on predefined criteria. The main advantages include:\n\n1. **Speed**: Algorithms can analyze market conditions and execute orders in milliseconds\n2. **Emotion-free**: Removes psychological biases from trading decisions\n3. **Backtesting**: Strategies can be tested on historical data before risking real capital\n4. **Consistency**: Executes trades exactly as programmed\n\nCommon strategies include market making, arbitrage, trend following, and mean reversion.", "**Risk management** is crucial in trading. Key principles include:\n\n- **Position sizing**: Never risk more than 1-2% of capital on a single trade\n- **Stop losses**: Set predetermined exit points to limit losses\n- **Diversification**: Spread risk across multiple assets or strategies\n- **Risk-reward ratio**: Aim for at least 2:1 reward-to-risk\n\nThe goal is to preserve capital during losing streaks while maximizing gains during winning periods.", "**Market volatility** refers to the rate and magnitude of price changes in a financial instrument. High volatility means larger price swings, while low volatility indicates stability.\n\nTraders measure volatility using:\n- **Standard deviation** of returns\n- **Average True Range (ATR)**\n- **Bollinger Bands**\n- **VIX index** (for equity markets)\n\nVolatility creates both opportunities and risks - more movement means more profit potential but also higher risk.", ], strategy: [ "When analyzing a **mean reversion strategy**, consider these key metrics:\n\n**Performance Metrics:**\n- Sharpe Ratio: Risk-adjusted returns (target > 1.5)\n- Maximum Drawdown: Largest peak-to-trough decline (keep < 20%)\n- Win Rate: Percentage of profitable trades\n- Profit Factor: Gross profit / Gross loss (target > 1.5)\n\n**Strategy-Specific:**\n- Mean reversion typically works best in range-bound markets\n- Watch for regime changes where markets shift from mean-reverting to trending\n- Consider transaction costs - they can significantly impact high-frequency mean reversion strategies", "**Backtesting results** should be interpreted carefully to avoid overfitting:\n\n**Red flags:**\n- Too many parameters (> 5-6 tunable variables)\n- Perfect or near-perfect equity curve\n- Performance degrades significantly on out-of-sample data\n- Very high Sharpe ratio (> 3) - often indicates curve fitting\n\n**Best practices:**\n- Use walk-forward analysis\n- Test on multiple market regimes\n- Include realistic transaction costs and slippage\n- Reserve 30-40% of data for out-of-sample testing\n\nRemember: Past performance does not guarantee future results.", "For **momentum strategies**, optimization should focus on:\n\n**Entry signals:**\n- Lookback period for momentum calculation (20-250 days typical)\n- Threshold for signal generation (% gain required)\n- Volume confirmation requirements\n\n**Risk management:**\n- Trailing stops to protect profits\n- Maximum holding period\n- Correlation filters to avoid overcrowding\n\n**Portfolio construction:**\n- Number of positions (5-20 for diversification)\n- Position sizing (equal weight vs volatility-adjusted)\n- Rebalancing frequency (weekly to monthly)\n\nMomentum works well in trending markets but can suffer during reversals.", "**Strategy robustness** can be evaluated through:\n\n1. **Parameter sensitivity**: Test nearby parameter values - robust strategies shouldn't degrade rapidly\n2. **Market regime analysis**: Performance across bull/bear/sideways markets\n3. **Asset class generalization**: Does it work on similar instruments?\n4. **Time frame stability**: Consistent performance across different time periods\n5. **Monte Carlo simulation**: Randomize trade sequences to assess statistical significance\n\nA robust strategy maintains profitability across reasonable parameter ranges and different market conditions.", ], } // Mock streaming function async function* mockChatStream( _question: string, mode: ChatMode ): AsyncGenerator { const sessionId = crypto.randomUUID() yield { type: 'session_id', data: sessionId } // Select a random response based on mode const responses = MOCK_RESPONSES[mode] const response = responses[Math.floor(Math.random() * responses.length)] // Simulate streaming by yielding words with delays const words = response.split(' ') for (let i = 0; i < words.length; i++) { // Add space before word (except first word) const chunk = i === 0 ? words[i] : ' ' + words[i] yield { type: 'chunk', data: chunk } // Random delay between 30-80ms to simulate typing await new Promise(resolve => setTimeout(resolve, Math.random() * 50 + 30)) } yield { type: 'done', data: null } } // SSE stream event types export type StreamEvent = | { type: 'chunk'; data: string } | { type: 'done'; data: null } | { type: 'error'; data: string } | { type: 'session_id'; data: string } class ApiClient { private sessionId: string | null = null getSessionId(): string | null { return this.sessionId || getChatSessionId() } setSessionId(sessionId: string): void { this.sessionId = sessionId setChatSessionId(sessionId) } clearSessionId(): void { this.sessionId = null clearChatSessionId() } // Streaming chat endpoint (SSE) async *chatStream( question: string, mode: ChatMode, sessionId?: string ): AsyncGenerator { // Use mock data if enabled if (USE_MOCK_DATA) { yield* mockChatStream(question, mode) return } // Generate or reuse session ID const actualSessionId = sessionId || this.getSessionId() || crypto.randomUUID() this.setSessionId(actualSessionId) // Yield session ID first yield { type: 'session_id', data: actualSessionId } try { const response = await fetch(`${API_BASE_URL}${API_ENDPOINT}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: question, conversation_id: actualSessionId, }), }) if (!response.ok) { yield { type: 'error', data: `HTTP error! status: ${response.status}` } return } if (!response.body) { yield { type: 'error', data: 'Response body is null' } return } const reader = response.body.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() || '' for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6) try { const parsed = JSON.parse(data) if (parsed.type === 'chunk' && parsed.content) { yield { type: 'chunk', data: parsed.content } } else if (parsed.type === 'done') { yield { type: 'done', data: null } return } else if (parsed.type === 'error') { yield { type: 'error', data: parsed.message || 'Unknown error' } return } } catch { // Skip non-JSON lines } } } } yield { type: 'done', data: null } } catch (error) { yield { type: 'error', data: error instanceof Error ? error.message : 'Network error', } } } } export const apiClient = new ApiClient()