import { Clock, Activity, TrendingUp, BarChart2, Info, Lock, ExternalLink, Zap, Check, AlertCircle, Key } from 'lucide-react' import type { IndicatorConfig } from '../../types' import { indicator, ts } from '../../i18n/strategy-translations' // Default NofxOS API Key const DEFAULT_NOFXOS_API_KEY = 'cm_568c67eae410d912c54c' interface IndicatorEditorProps { config: IndicatorConfig onChange: (config: IndicatorConfig) => void disabled?: boolean language: string } // All available timeframes const allTimeframes = [ { value: '1m', label: '1m', category: 'scalp' }, { value: '3m', label: '3m', category: 'scalp' }, { value: '5m', label: '5m', category: 'scalp' }, { value: '15m', label: '15m', category: 'intraday' }, { value: '30m', label: '30m', category: 'intraday' }, { value: '1h', label: '1h', category: 'intraday' }, { value: '2h', label: '2h', category: 'swing' }, { value: '4h', label: '4h', category: 'swing' }, { value: '6h', label: '6h', category: 'swing' }, { value: '8h', label: '8h', category: 'swing' }, { value: '12h', label: '12h', category: 'swing' }, { value: '1d', label: '1D', category: 'position' }, { value: '3d', label: '3D', category: 'position' }, { value: '1w', label: '1W', category: 'position' }, ] export function IndicatorEditor({ config, onChange, disabled, language, }: IndicatorEditorProps) { // Get currently selected timeframes const selectedTimeframes = config.klines.selected_timeframes || [config.klines.primary_timeframe] // Toggle timeframe selection const toggleTimeframe = (tf: string) => { if (disabled) return const current = [...selectedTimeframes] const index = current.indexOf(tf) if (index >= 0) { if (current.length > 1) { current.splice(index, 1) const newPrimary = tf === config.klines.primary_timeframe ? current[0] : config.klines.primary_timeframe onChange({ ...config, klines: { ...config.klines, selected_timeframes: current, primary_timeframe: newPrimary, enable_multi_timeframe: current.length > 1, }, }) } } else { current.push(tf) onChange({ ...config, klines: { ...config.klines, selected_timeframes: current, enable_multi_timeframe: current.length > 1, }, }) } } // Set primary timeframe const setPrimaryTimeframe = (tf: string) => { if (disabled) return onChange({ ...config, klines: { ...config.klines, primary_timeframe: tf, }, }) } const categoryColors: Record = { scalp: '#F6465D', intraday: '#F0B90B', swing: '#0ECB81', position: '#60a5fa', } // Ensure enable_raw_klines is always true const ensureRawKlines = () => { if (!config.enable_raw_klines) { onChange({ ...config, enable_raw_klines: true }) } } // Call on mount if needed if (config.enable_raw_klines === undefined || config.enable_raw_klines === false) { ensureRawKlines() } // Check if any NofxOS feature is enabled const hasNofxosEnabled = config.enable_quant_data || config.enable_oi_ranking || config.enable_netflow_ranking || config.enable_price_ranking const hasApiKey = !!config.nofxos_api_key return (
{/* ============================================ */} {/* NofxOS Data Provider - Top Configuration */} {/* ============================================ */}
{/* Decorative gradient line at top */}
{/* Header Row */}

{ts(indicator.nofxosTitle, language)}

{ts(indicator.nofxosFeatures, language)}
{/* Status & API Docs */}
{hasApiKey ? ( {ts(indicator.connected, language)} ) : ( {ts(indicator.notConfigured, language)} )} {ts(indicator.viewApiDocs, language)}
{/* API Key Input */}
!disabled && onChange({ ...config, nofxos_api_key: e.target.value })} disabled={disabled} placeholder={ts(indicator.apiKeyPlaceholder, language)} className="w-full pl-9 pr-3 py-2 rounded-lg text-sm font-mono" style={{ background: 'rgba(30, 35, 41, 0.8)', border: hasApiKey ? '1px solid rgba(14, 203, 129, 0.3)' : '1px solid rgba(139, 92, 246, 0.3)', color: '#EAECEF', }} />
{!disabled && !config.nofxos_api_key && ( )}
{/* NofxOS Data Sources Grid */}
{ts(indicator.nofxosDataSources, language)}
{/* Quant Data */}
!disabled && onChange({ ...config, enable_quant_data: !config.enable_quant_data })} >
{ts(indicator.quantData, language)}
{ e.stopPropagation(); !disabled && onChange({ ...config, enable_quant_data: e.target.checked }) }} disabled={disabled} className="w-3.5 h-3.5 rounded accent-blue-500" />

{ts(indicator.quantDataDesc, language)}

{config.enable_quant_data && (
)}
{/* OI Ranking */}
!disabled && onChange({ ...config, enable_oi_ranking: !config.enable_oi_ranking, ...(!config.enable_oi_ranking && !config.oi_ranking_duration ? { oi_ranking_duration: '1h' } : {}), ...(!config.enable_oi_ranking && !config.oi_ranking_limit ? { oi_ranking_limit: 10 } : {}), })} >
{ts(indicator.oiRanking, language)}
{ e.stopPropagation(); !disabled && onChange({ ...config, enable_oi_ranking: e.target.checked, ...(e.target.checked && !config.oi_ranking_duration ? { oi_ranking_duration: '1h' } : {}), ...(e.target.checked && !config.oi_ranking_limit ? { oi_ranking_limit: 10 } : {}), }) }} disabled={disabled} className="w-3.5 h-3.5 rounded accent-green-500" />

{ts(indicator.oiRankingDesc, language)}

{config.enable_oi_ranking && (
e.stopPropagation()}>
)}
{/* NetFlow Ranking */}
!disabled && onChange({ ...config, enable_netflow_ranking: !config.enable_netflow_ranking, ...(!config.enable_netflow_ranking && !config.netflow_ranking_duration ? { netflow_ranking_duration: '1h' } : {}), ...(!config.enable_netflow_ranking && !config.netflow_ranking_limit ? { netflow_ranking_limit: 10 } : {}), })} >
{ts(indicator.netflowRanking, language)}
{ e.stopPropagation(); !disabled && onChange({ ...config, enable_netflow_ranking: e.target.checked, ...(e.target.checked && !config.netflow_ranking_duration ? { netflow_ranking_duration: '1h' } : {}), ...(e.target.checked && !config.netflow_ranking_limit ? { netflow_ranking_limit: 10 } : {}), }) }} disabled={disabled} className="w-3.5 h-3.5 rounded accent-amber-500" />

{ts(indicator.netflowRankingDesc, language)}

{config.enable_netflow_ranking && (
e.stopPropagation()}>
)}
{/* Price Ranking */}
!disabled && onChange({ ...config, enable_price_ranking: !config.enable_price_ranking, ...(!config.enable_price_ranking && !config.price_ranking_duration ? { price_ranking_duration: '1h,4h,24h' } : {}), ...(!config.enable_price_ranking && !config.price_ranking_limit ? { price_ranking_limit: 10 } : {}), })} >
{ts(indicator.priceRanking, language)}
{ e.stopPropagation(); !disabled && onChange({ ...config, enable_price_ranking: e.target.checked, ...(e.target.checked && !config.price_ranking_duration ? { price_ranking_duration: '1h,4h,24h' } : {}), ...(e.target.checked && !config.price_ranking_limit ? { price_ranking_limit: 10 } : {}), }) }} disabled={disabled} className="w-3.5 h-3.5 rounded accent-pink-500" />

{ts(indicator.priceRankingDesc, language)}

{config.enable_price_ranking && (
e.stopPropagation()}>
)}
{/* Warning if features enabled but no API key */} {hasNofxosEnabled && !hasApiKey && (
{ts(indicator.configureApiKey, language)}
)}
{/* ============================================ */} {/* Section 1: Market Data (Required) */} {/* ============================================ */}
{ts(indicator.marketData, language)} - {ts(indicator.marketDataDesc, language)}
{/* Raw Klines - Required, Always On */}
{ts(indicator.rawKlines, language)} {ts(indicator.required, language)}

{ts(indicator.rawKlinesDesc, language)}

{/* Timeframe Selection */}
{ts(indicator.timeframes, language)}
{ts(indicator.klineCount, language)}: !disabled && onChange({ ...config, klines: { ...config.klines, primary_count: parseInt(e.target.value) || 30 }, }) } disabled={disabled} min={10} max={200} className="w-16 px-2 py-1 rounded text-xs text-center" style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }} />

{ts(indicator.timeframesDesc, language)}

{/* Timeframe Grid */}
{(['scalp', 'intraday', 'swing', 'position'] as const).map((category) => { const categoryTfs = allTimeframes.filter((tf) => tf.category === category) return (
{ts(indicator[category], language)}
{categoryTfs.map((tf) => { const isSelected = selectedTimeframes.includes(tf.value) const isPrimary = config.klines.primary_timeframe === tf.value return ( ) })}
) })}
{/* ============================================ */} {/* Section 2: Technical Indicators (Optional) */} {/* ============================================ */}
{ts(indicator.technicalIndicators, language)} - {ts(indicator.technicalIndicatorsDesc, language)}
{/* Tip */}

{ts(indicator.aiCanCalculate, language)}

{/* Indicator Grid */}
{[ { key: 'enable_ema', label: 'ema', desc: 'emaDesc', color: '#F0B90B', periodKey: 'ema_periods', defaultPeriods: '20,50' }, { key: 'enable_macd', label: 'macd', desc: 'macdDesc', color: '#a855f7' }, { key: 'enable_rsi', label: 'rsi', desc: 'rsiDesc', color: '#F6465D', periodKey: 'rsi_periods', defaultPeriods: '7,14' }, { key: 'enable_atr', label: 'atr', desc: 'atrDesc', color: '#60a5fa', periodKey: 'atr_periods', defaultPeriods: '14' }, { key: 'enable_boll', label: 'boll', desc: 'bollDesc', color: '#ec4899', periodKey: 'boll_periods', defaultPeriods: '20' }, ].map(({ key, label, desc, color, periodKey, defaultPeriods }) => (
{ts(indicator[label as keyof typeof indicator], language)}
!disabled && onChange({ ...config, [key]: e.target.checked })} disabled={disabled} className="w-4 h-4 rounded accent-yellow-500" />

{ts(indicator[desc as keyof typeof indicator], language)}

{periodKey && config[key as keyof IndicatorConfig] && ( { if (disabled) return const periods = e.target.value .split(',') .map((s) => parseInt(s.trim())) .filter((n) => !isNaN(n) && n > 0) onChange({ ...config, [periodKey]: periods }) }} disabled={disabled} placeholder={defaultPeriods} className="w-full px-2 py-1 rounded text-[10px] text-center" style={{ background: '#1E2329', border: '1px solid #2B3139', color: '#EAECEF' }} /> )}
))}
{/* ============================================ */} {/* Section 3: Market Sentiment */} {/* ============================================ */}
{ts(indicator.marketSentiment, language)} - {ts(indicator.marketSentimentDesc, language)}
{[ { key: 'enable_volume', label: 'volume', desc: 'volumeDesc', color: '#c084fc' }, { key: 'enable_oi', label: 'oi', desc: 'oiDesc', color: '#34d399' }, { key: 'enable_funding_rate', label: 'fundingRate', desc: 'fundingRateDesc', color: '#fbbf24' }, ].map(({ key, label, desc, color }) => (
{ts(indicator[label as keyof typeof indicator], language)}
!disabled && onChange({ ...config, [key]: e.target.checked })} disabled={disabled} className="w-4 h-4 rounded accent-yellow-500" />

{ts(indicator[desc as keyof typeof indicator], language)}

))}
) }