策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":10}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-832,1008],\"id\":\"51bf46b2-aa9d-473b-9ecb-b46c117ccc7d\",\"name\":\"Schedule Trigger\"},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"anthropic/claude-sonnet-4.5\",\"mode\":\"list\",\"cachedResultName\":\"anthropic/claude-sonnet-4.5\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[-64,528],\"id\":\"e446f6ce-c557-446b-9046-ade09823003f\",\"name\":\"Claude4.5\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"deepseek/deepseek-chat-v3.1\",\"mode\":\"list\",\"cachedResultName\":\"deepseek/deepseek-chat-v3.1\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[-64,128],\"id\":\"7f39c609-676b-40e2-b025-2df3aaf326b1\",\"name\":\"DS3.1\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"text\":\"=You are a professional cryptocurrency quantitative trading analyst. Please provide trading decision recommendations based on the following multi-timeframe technical indicators, current position information, and competitive ranking.\\n\\n## Competition Status\\nYou are the {{ $json.ranking.currentModel }} AI model, currently ranked {{ $json.ranking.currentRank }} out of {{ $json.ranking.totalModels }} AI models in the trading competition.\\n{{$json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition just started! This is a great opportunity to establish profit foundation! Actively seek quality trading opportunities in the initial phase!' : ''}}\\n\\n### Current Performance\\n- Your Realized PnL: {{ $json.ranking.currentPnl }} USDT {{ $json.ranking.currentPnl === 0 ? '(Initial State)' : '' }}\\n- Your Total Asset Value: {{ $json.ranking.currentTotalValue }} USDT\\n- Total Trades: {{ $json.ranking.totalTrades }} {{ $json.ranking.totalTrades === 0 ? '(No trades yet)' : '' }}\\n- Win Rate: {{ $json.ranking.winRate }}%{{ $json.ranking.totalTrades === 0 ? ' (No data)' : '' }}\\n\\n### Competitor Rankings\\n{{ $json.ranking.rankingText }}\\n{{ $json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition begins!!!' : $json.ranking.gapInfo}}\\n\\n## Current Data Overview\\n- Trading Pair: {{ $json.symbol }}\\n- Analysis Time: {{ new Date($json.timestamp).toLocaleString() }}\\n- Current Position: {{ $json.position && $json.position.length > 0 ? 'Has Position' : 'No Position' }}\\n\\n## Technical Indicators Data (OLDEST-NEWEST)\\n### Daily Timeframe (Trend Analysis)\\n- Price: {{ $json.timeframes.day?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.day?.RSI || 'N/A' }} \\n- MACD Histogram: {{ $json.timeframes.day?.MACD.histogram || 'N/A' }} \\n- ATR: {{ $json.timeframes.day?.ATR || 'N/A' }} \\n- OBV: {{ $json.timeframes.day?.OBV || 'N/A' }} \\n\\n### Hourly Timeframe (Mid-term Trend)\\n- Price: {{ $json.timeframes.hour?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.hour?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.hour?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.hour?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.hour?.OBV || 'N/A' }}\\n\\n### 5-Minute Timeframe (Entry Timing)\\n- Price: {{ $json.timeframes.min5?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.min5?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.min5?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.min5?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.min5?.OBV || 'N/A' }}\\n\\n### Current Position Info\\n{{JSON.stringify($json.position, null, 2)}}\\n\\n## Analysis Requirements\\nDevelop optimal trading strategy based on technical indicators and your competitive ranking. Your goal is to continuously create more profit, not simply maintain ranking. {{ $json.ranking.totalTrades === 0 ? 'As the competition starts, actively seek high-quality trading opportunities to establish profit foundation.' : 'Continue seeking quality trading opportunities to expand profits, avoid being overly conservative and missing profit chances.' }}\\n\\n### Strategy Recommendations:\\n{{ $json.ranking.totalTrades === 0 ? '- Competition just started, actively find best entry timing to establish profit foundation' : $json.ranking.currentRank > 1 ? '- You are currently behind, actively seek more quality trading opportunities to catch up and create profits' : '- You are performing well, continue active trading to expand profit advantage' }}\\n- Carefully analyze multi-timeframe indicator consistency\\n- Execute trades decisively when technical signals are clear\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade should establish good start' : 'Continue seeking profit opportunities, avoid being overly conservative' }}\\n- Focus on risk-reward ratio, not ranking protection\\n\\n### Executable Actions:\\n- **OPEN_LONG**: Open long position (when no position)\\n- **OPEN_SHORT**: Open short position (when no position) \\n- **CLOSE_LONG**: Close long position (when holding long)\\n- **CLOSE_SHORT**: Close short position (when holding short)\\n- **NO_ACTION**: Wait and watch (when technical signals unclear)\\n\\n### Execution Criteria:\\n- Prioritize profit creation opportunities\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade requires establishing profit foundation' : 'Continue seeking high-quality trading opportunities to expand total profits' }}\\n- Balance risk control with profit pursuit\\n- Only choose NO_ACTION when technical signals clearly conflict\\n\\n## Return Format\\n```json\\n{\\n \\\"action\\\": \\\"OPEN_LONG | OPEN_SHORT | CLOSE_LONG | CLOSE_SHORT | NO_ACTION\\\",\\n \\\"reasoning\\\": \\\"Concise decision rationale combining technical indicators and profit creation goals\\\",\\n \\\"modelname\\\": \\\"{{ $json.ranking.currentModel }}\\\"\\n}\\n```\\nProvide direct judgment.\",\"options\":{}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[-160,-96],\"id\":\"ccff925e-e79a-4865-a204-6348cb37ac5f\",\"name\":\"DeepSeek\",\"retryOnFail\":true,\"onError\":\"continueRegularOutput\"},{\"parameters\":{\"text\":\"=You are a professional cryptocurrency quantitative trading analyst. Please provide trading decision recommendations based on the following multi-timeframe technical indicators, current position information, and competitive ranking.\\n\\n## Competition Status\\nYou are the {{ $json.ranking.currentModel }} AI model, currently ranked {{ $json.ranking.currentRank }} out of {{ $json.ranking.totalModels }} AI models in the trading competition.\\n{{$json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition just started! This is a great opportunity to establish profit foundation! Actively seek quality trading opportunities in the initial phase!' : ''}}\\n\\n### Current Performance\\n- Your Realized PnL: {{ $json.ranking.currentPnl }} USDT {{ $json.ranking.currentPnl === 0 ? '(Initial State)' : '' }}\\n- Your Total Asset Value: {{ $json.ranking.currentTotalValue }} USDT\\n- Total Trades: {{ $json.ranking.totalTrades }} {{ $json.ranking.totalTrades === 0 ? '(No trades yet)' : '' }}\\n- Win Rate: {{ $json.ranking.winRate }}%{{ $json.ranking.totalTrades === 0 ? ' (No data)' : '' }}\\n\\n### Competitor Rankings\\n{{ $json.ranking.rankingText }}\\n{{ $json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition begins!!!' : $json.ranking.gapInfo}}\\n\\n## Current Data Overview\\n- Trading Pair: {{ $json.symbol }}\\n- Analysis Time: {{ new Date($json.timestamp).toLocaleString() }}\\n- Current Position: {{ $json.position && $json.position.length > 0 ? 'Has Position' : 'No Position' }}\\n\\n## Technical Indicators Data (OLDEST-NEWEST)\\n### Daily Timeframe (Trend Analysis)\\n- Price: {{ $json.timeframes.day?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.day?.RSI || 'N/A' }} \\n- MACD Histogram: {{ $json.timeframes.day?.MACD.histogram || 'N/A' }} \\n- ATR: {{ $json.timeframes.day?.ATR || 'N/A' }} \\n- OBV: {{ $json.timeframes.day?.OBV || 'N/A' }} \\n\\n### Hourly Timeframe (Mid-term Trend)\\n- Price: {{ $json.timeframes.hour?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.hour?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.hour?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.hour?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.hour?.OBV || 'N/A' }}\\n\\n### 5-Minute Timeframe (Entry Timing)\\n- Price: {{ $json.timeframes.min5?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.min5?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.min5?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.min5?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.min5?.OBV || 'N/A' }}\\n\\n### Current Position Info\\n{{JSON.stringify($json.position, null, 2)}}\\n\\n## Analysis Requirements\\nDevelop optimal trading strategy based on technical indicators and your competitive ranking. Your goal is to continuously create more profit, not simply maintain ranking. {{ $json.ranking.totalTrades === 0 ? 'As the competition starts, actively seek high-quality trading opportunities to establish profit foundation.' : 'Continue seeking quality trading opportunities to expand profits, avoid being overly conservative and missing profit chances.' }}\\n\\n### Strategy Recommendations:\\n{{ $json.ranking.totalTrades === 0 ? '- Competition just started, actively find best entry timing to establish profit foundation' : $json.ranking.currentRank > 1 ? '- You are currently behind, actively seek more quality trading opportunities to catch up and create profits' : '- You are performing well, continue active trading to expand profit advantage' }}\\n- Carefully analyze multi-timeframe indicator consistency\\n- Execute trades decisively when technical signals are clear\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade should establish good start' : 'Continue seeking profit opportunities, avoid being overly conservative' }}\\n- Focus on risk-reward ratio, not ranking protection\\n\\n### Executable Actions:\\n- **OPEN_LONG**: Open long position (when no position)\\n- **OPEN_SHORT**: Open short position (when no position) \\n- **CLOSE_LONG**: Close long position (when holding long)\\n- **CLOSE_SHORT**: Close short position (when holding short)\\n- **NO_ACTION**: Wait and watch (when technical signals unclear)\\n\\n### Execution Criteria:\\n- Prioritize profit creation opportunities\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade requires establishing profit foundation' : 'Continue seeking high-quality trading opportunities to expand total profits' }}\\n- Balance risk control with profit pursuit\\n- Only choose NO_ACTION when technical signals clearly conflict\\n\\n## Return Format\\n```json\\n{\\n \\\"action\\\": \\\"OPEN_LONG | OPEN_SHORT | CLOSE_LONG | CLOSE_SHORT | NO_ACTION\\\",\\n \\\"reasoning\\\": \\\"Concise decision rationale combining technical indicators and profit creation goals\\\",\\n \\\"modelname\\\": \\\"{{ $json.ranking.currentModel }}\\\"\\n}\\n```\\nProvide direct judgment.\",\"options\":{}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[-160,304],\"id\":\"b0a0814f-d64b-4bb5-b1e2-bc05b03f9be3\",\"name\":\"Claude\",\"retryOnFail\":true,\"onError\":\"continueRegularOutput\"},{\"parameters\":{\"text\":\"=You are a professional cryptocurrency quantitative trading analyst. Please provide trading decision recommendations based on the following multi-timeframe technical indicators, current position information, and competitive ranking.\\n\\n## Competition Status\\nYou are the {{ $json.ranking.currentModel }} AI model, currently ranked {{ $json.ranking.currentRank }} out of {{ $json.ranking.totalModels }} AI models in the trading competition.\\n{{$json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition just started! This is a great opportunity to establish profit foundation! Actively seek quality trading opportunities in the initial phase!' : ''}}\\n\\n### Current Performance\\n- Your Realized PnL: {{ $json.ranking.currentPnl }} USDT {{ $json.ranking.currentPnl === 0 ? '(Initial State)' : '' }}\\n- Your Total Asset Value: {{ $json.ranking.currentTotalValue }} USDT\\n- Total Trades: {{ $json.ranking.totalTrades }} {{ $json.ranking.totalTrades === 0 ? '(No trades yet)' : '' }}\\n- Win Rate: {{ $json.ranking.winRate }}%{{ $json.ranking.totalTrades === 0 ? ' (No data)' : '' }}\\n\\n### Competitor Rankings\\n{{ $json.ranking.rankingText }}\\n{{ $json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition begins!!!' : $json.ranking.gapInfo}}\\n\\n## Current Data Overview\\n- Trading Pair: {{ $json.symbol }}\\n- Analysis Time: {{ new Date($json.timestamp).toLocaleString() }}\\n- Current Position: {{ $json.position && $json.position.length > 0 ? 'Has Position' : 'No Position' }}\\n\\n## Technical Indicators Data (OLDEST-NEWEST)\\n### Daily Timeframe (Trend Analysis)\\n- Price: {{ $json.timeframes.day?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.day?.RSI || 'N/A' }} \\n- MACD Histogram: {{ $json.timeframes.day?.MACD.histogram || 'N/A' }} \\n- ATR: {{ $json.timeframes.day?.ATR || 'N/A' }} \\n- OBV: {{ $json.timeframes.day?.OBV || 'N/A' }} \\n\\n### Hourly Timeframe (Mid-term Trend)\\n- Price: {{ $json.timeframes.hour?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.hour?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.hour?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.hour?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.hour?.OBV || 'N/A' }}\\n\\n### 5-Minute Timeframe (Entry Timing)\\n- Price: {{ $json.timeframes.min5?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.min5?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.min5?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.min5?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.min5?.OBV || 'N/A' }}\\n\\n### Current Position Info\\n{{JSON.stringify($json.position, null, 2)}}\\n\\n## Analysis Requirements\\nDevelop optimal trading strategy based on technical indicators and your competitive ranking. Your goal is to continuously create more profit, not simply maintain ranking. {{ $json.ranking.totalTrades === 0 ? 'As the competition starts, actively seek high-quality trading opportunities to establish profit foundation.' : 'Continue seeking quality trading opportunities to expand profits, avoid being overly conservative and missing profit chances.' }}\\n\\n### Strategy Recommendations:\\n{{ $json.ranking.totalTrades === 0 ? '- Competition just started, actively find best entry timing to establish profit foundation' : $json.ranking.currentRank > 1 ? '- You are currently behind, actively seek more quality trading opportunities to catch up and create profits' : '- You are performing well, continue active trading to expand profit advantage' }}\\n- Carefully analyze multi-timeframe indicator consistency\\n- Execute trades decisively when technical signals are clear\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade should establish good start' : 'Continue seeking profit opportunities, avoid being overly conservative' }}\\n- Focus on risk-reward ratio, not ranking protection\\n\\n### Executable Actions:\\n- **OPEN_LONG**: Open long position (when no position)\\n- **OPEN_SHORT**: Open short position (when no position) \\n- **CLOSE_LONG**: Close long position (when holding long)\\n- **CLOSE_SHORT**: Close short position (when holding short)\\n- **NO_ACTION**: Wait and watch (when technical signals unclear)\\n\\n### Execution Criteria:\\n- Prioritize profit creation opportunities\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade requires establishing profit foundation' : 'Continue seeking high-quality trading opportunities to expand total profits' }}\\n- Balance risk control with profit pursuit\\n- Only choose NO_ACTION when technical signals clearly conflict\\n\\n## Return Format\\n```json\\n{\\n \\\"action\\\": \\\"OPEN_LONG | OPEN_SHORT | CLOSE_LONG | CLOSE_SHORT | NO_ACTION\\\",\\n \\\"reasoning\\\": \\\"Concise decision rationale combining technical indicators and profit creation goals\\\",\\n \\\"modelname\\\": \\\"{{ $json.ranking.currentModel }}\\\"\\n}\\n```\\nProvide direct judgment.\",\"options\":{}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[-160,704],\"id\":\"c0b74a78-1494-4495-ac64-3bd4858fc3a3\",\"name\":\"QWEN\",\"retryOnFail\":true,\"onError\":\"continueRegularOutput\"},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"qwen/qwen3-max\",\"mode\":\"list\",\"cachedResultName\":\"qwen/qwen3-max\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[-64,928],\"id\":\"4df0f0ac-9736-46ff-aeeb-5f1ddbdea9c9\",\"name\":\"OpenAI Model\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"text\":\"=You are a professional cryptocurrency quantitative trading analyst. Please provide trading decision recommendations based on the following multi-timeframe technical indicators, current position information, and competitive ranking.\\n\\n## Competition Status\\nYou are the {{ $json.ranking.currentModel }} AI model, currently ranked {{ $json.ranking.currentRank }} out of {{ $json.ranking.totalModels }} AI models in the trading competition.\\n{{$json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition just started! This is a great opportunity to establish profit foundation! Actively seek quality trading opportunities in the initial phase!' : ''}}\\n\\n### Current Performance\\n- Your Realized PnL: {{ $json.ranking.currentPnl }} USDT {{ $json.ranking.currentPnl === 0 ? '(Initial State)' : '' }}\\n- Your Total Asset Value: {{ $json.ranking.currentTotalValue }} USDT\\n- Total Trades: {{ $json.ranking.totalTrades }} {{ $json.ranking.totalTrades === 0 ? '(No trades yet)' : '' }}\\n- Win Rate: {{ $json.ranking.winRate }}%{{ $json.ranking.totalTrades === 0 ? ' (No data)' : '' }}\\n\\n### Competitor Rankings\\n{{ $json.ranking.rankingText }}\\n{{ $json.ranking.currentPnl === 0 && $json.ranking.totalTrades === 0 ? 'Competition begins!!!' : $json.ranking.gapInfo}}\\n\\n## Current Data Overview\\n- Trading Pair: {{ $json.symbol }}\\n- Analysis Time: {{ new Date($json.timestamp).toLocaleString() }}\\n- Current Position: {{ $json.position && $json.position.length > 0 ? 'Has Position' : 'No Position' }}\\n\\n## Technical Indicators Data (OLDEST-NEWEST)\\n### Daily Timeframe (Trend Analysis)\\n- Price: {{ $json.timeframes.day?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.day?.RSI || 'N/A' }} \\n- MACD Histogram: {{ $json.timeframes.day?.MACD.histogram || 'N/A' }} \\n- ATR: {{ $json.timeframes.day?.ATR || 'N/A' }} \\n- OBV: {{ $json.timeframes.day?.OBV || 'N/A' }} \\n\\n### Hourly Timeframe (Mid-term Trend)\\n- Price: {{ $json.timeframes.hour?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.hour?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.hour?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.hour?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.hour?.OBV || 'N/A' }}\\n\\n### 5-Minute Timeframe (Entry Timing)\\n- Price: {{ $json.timeframes.min5?.lastPrice || 'N/A' }}\\n- RSI(14): {{ $json.timeframes.min5?.RSI || 'N/A' }}\\n- MACD Histogram: {{ $json.timeframes.min5?.MACD.histogram || 'N/A' }}\\n- ATR: {{ $json.timeframes.min5?.ATR || 'N/A' }}\\n- OBV: {{ $json.timeframes.min5?.OBV || 'N/A' }}\\n\\n### Current Position Info\\n{{JSON.stringify($json.position, null, 2)}}\\n\\n## Analysis Requirements\\nDevelop optimal trading strategy based on technical indicators and your competitive ranking. Your goal is to continuously create more profit, not simply maintain ranking. {{ $json.ranking.totalTrades === 0 ? 'As the competition starts, actively seek high-quality trading opportunities to establish profit foundation.' : 'Continue seeking quality trading opportunities to expand profits, avoid being overly conservative and missing profit chances.' }}\\n\\n### Strategy Recommendations:\\n{{ $json.ranking.totalTrades === 0 ? '- Competition just started, actively find best entry timing to establish profit foundation' : $json.ranking.currentRank > 1 ? '- You are currently behind, actively seek more quality trading opportunities to catch up and create profits' : '- You are performing well, continue active trading to expand profit advantage' }}\\n- Carefully analyze multi-timeframe indicator consistency\\n- Execute trades decisively when technical signals are clear\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade should establish good start' : 'Continue seeking profit opportunities, avoid being overly conservative' }}\\n- Focus on risk-reward ratio, not ranking protection\\n\\n### Executable Actions:\\n- **OPEN_LONG**: Open long position (when no position)\\n- **OPEN_SHORT**: Open short position (when no position) \\n- **CLOSE_LONG**: Close long position (when holding long)\\n- **CLOSE_SHORT**: Close short position (when holding short)\\n- **NO_ACTION**: Wait and watch (when technical signals unclear)\\n\\n### Execution Criteria:\\n- Prioritize profit creation opportunities\\n- {{ $json.ranking.totalTrades === 0 ? 'First trade requires establishing profit foundation' : 'Continue seeking high-quality trading opportunities to expand total profits' }}\\n- Balance risk control with profit pursuit\\n- Only choose NO_ACTION when technical signals clearly conflict\\n\\n## Return Format\\n```json\\n{\\n \\\"action\\\": \\\"OPEN_LONG | OPEN_SHORT | CLOSE_LONG | CLOSE_SHORT | NO_ACTION\\\",\\n \\\"reasoning\\\": \\\"Concise decision rationale combining technical indicators and profit creation goals\\\",\\n \\\"modelname\\\": \\\"{{ $json.ranking.currentModel }}\\\"\\n}\\n```\\nProvide direct judgment.\",\"options\":{}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[-160,1104],\"id\":\"310d7b46-2333-42c5-9c75-753f6f2e2529\",\"name\":\"Grok\",\"retryOnFail\":true,\"onError\":\"continueRegularOutput\"},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"x-ai/grok-4\",\"mode\":\"list\",\"cachedResultName\":\"x-ai/grok-4\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[-64,1328],\"id\":\"3dabb427-58b9-47b0-ac28-6fb1df083605\",\"name\":\"Grok4\",\"credentials\":{\"openAiApi\":{\"id\":\"54d0b567-b3fc-4c6a-b6be-546e0b9cd83f\",\"name\":\"openrouter\"}}},{\"parameters\":{\"mode\":\"append\",\"numberInputs\":4},\"type\":\"n8n-nodes-base.merge\",\"typeVersion\":3.2,\"position\":[240,944],\"id\":\"ddae2251-7d25-4bee-aacd-f67ab3d8dbf0\",\"name\":\"Merge\"},{\"parameters\":{\"operation\":\"csv\",\"binaryPropertyName\":\"data\",\"options\":{}},\"type\":\"n8n-nodes-base.convertToFile\",\"typeVersion\":1.1,\"position\":[912,1104],\"id\":\"ec0705d4-47ee-4201-8019-3a1b0979b7e3\",\"name\":\"Convert to File\"},{\"parameters\":{\"info\":\"\",\"fileName\":\"tradelog.csv\",\"dataPropertyName\":\"data\",\"options\":{}},\"type\":\"n8n-nodes-base.writeFile\",\"typeVersion\":1,\"position\":[1136,1104],\"id\":\"d67538cf-24e2-47b3-9301-15067aaadcea\",\"name\":\"Write to File\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// Get AI decision results and clean data\\nconst aiResults = $input.all().map(item => {\\n if (item.json && item.json.output) {\\n return { output: item.json.output };\\n } else if (item.output) {\\n return { output: item.output };\\n }\\n return null;\\n}).filter(item => item !== null);\\n\\nexchange.SetContractType('swap') \\nconst ticker = _C(exchange.GetTicker);\\nconst fixedSize = $vars.size || 0.01; // Fixed trading size\\n\\nif (!ticker || !ticker.Last) {\\n Log(\\\"Failed to get price\\\");\\n return $input.all();\\n}\\n\\nconst currentPrice = ticker.Last;\\nconst timestamp = Date.now();\\n\\n// Get contract value\\nconst markets = _G('markets');\\nconst ctVal = markets ? markets.CtVal : 1;\\n\\n// Get model data - use deep copy to avoid modifying original data\\nconst models = JSON.parse(JSON.stringify($node['Strategy Init'].json));\\n\\n// Process each AI's decision\\naiResults.forEach(item => {\\n Log('Current item:', item)\\n if (!item.output){\\n return;\\n }\\n \\n try {\\n let decision;\\n \\n // Try to extract JSON wrapped in markdown\\n const jsonMatch = item.output.match(/```json\\\\s*([\\\\s\\\\S]*?)\\\\s*```/);\\n \\n if (jsonMatch) {\\n // Claude format: ```json ... ```\\n decision = JSON.parse(jsonMatch[1]);\\n } else {\\n // DS format: direct JSON\\n decision = JSON.parse(item.output);\\n }\\n \\n const modelKey = decision.modelname;\\n\\n if (!models || !models[modelKey]) {\\n Log(`Model not found: ${modelKey}`);\\n return;\\n }\\n \\n const model = models[modelKey];\\n \\n // Record decision\\n model.decisions.push({\\n timestamp,\\n action: decision.action,\\n reasoning: decision.reasoning,\\n price: currentPrice\\n });\\n \\n // ============ Virtual Position Trading Logic ============\\n \\n if (decision.action === 'OPEN_LONG') {\\n // Check if already has position (virtual position)\\n if (model.positions.length === 0) {\\n model.positions.push({\\n side: 'long',\\n size: fixedSize,\\n entryPrice: currentPrice,\\n ctVal: ctVal,\\n timestamp: timestamp\\n });\\n \\n model.account.totalTrades++;\\n \\n model.tradeHistory.push({\\n action: 'OPEN_LONG',\\n price: currentPrice,\\n size: fixedSize,\\n ctVal: ctVal,\\n timestamp: timestamp\\n });\\n \\n Log(`${modelKey} Virtual open long: ${currentPrice}, size: ${fixedSize}`);\\n }\\n }\\n \\n else if (decision.action === 'OPEN_SHORT') {\\n // Check if already has position (virtual position)\\n if (model.positions.length === 0) {\\n model.positions.push({\\n side: 'short',\\n size: fixedSize,\\n entryPrice: currentPrice,\\n ctVal: ctVal,\\n timestamp: timestamp\\n });\\n \\n model.account.totalTrades++;\\n \\n model.tradeHistory.push({\\n action: 'OPEN_SHORT',\\n price: currentPrice,\\n size: fixedSize,\\n ctVal: ctVal,\\n timestamp: timestamp\\n });\\n \\n Log(`${modelKey} Virtual open short: ${currentPrice}, size: ${fixedSize}`);\\n }\\n }\\n \\n else if (decision.action === 'CLOSE_LONG' || decision.action === 'CLOSE_SHORT') {\\n // Close position (virtual position)\\n const targetSide = decision.action === 'CLOSE_LONG' ? 'long' : 'short';\\n const posIndex = model.positions.findIndex(pos => pos.side === targetSide);\\n \\n if (posIndex !== -1) {\\n const position = model.positions[posIndex];\\n \\n // ✅ Fix: Calculate absolute PnL (don't divide by entryPrice)\\n const pnl = position.side === 'long' \\n ? (currentPrice - position.entryPrice) * position.size * position.ctVal\\n : (position.entryPrice - currentPrice) * position.size * position.ctVal;\\n \\n // Opening and closing fees\\n const openFee = position.size * position.ctVal * 0.001;\\n const closeFee = position.size * position.ctVal * 0.001;\\n const totalFee = openFee + closeFee;\\n \\n const netPnl = pnl - totalFee;\\n \\n // Accumulate to total PnL\\n model.account.realizedPnl += netPnl;\\n \\n if (netPnl > 0) model.account.winTrades++;\\n \\n model.tradeHistory.push({\\n action: decision.action,\\n price: currentPrice,\\n size: position.size,\\n ctVal: position.ctVal,\\n entryPrice: position.entryPrice,\\n pnl: netPnl,\\n fee: totalFee,\\n timestamp: timestamp\\n });\\n \\n Log(`${modelKey} Virtual close: ${currentPrice}, PnL: ${netPnl.toFixed(2)}`);\\n \\n // Remove virtual position\\n model.positions.splice(posIndex, 1);\\n }\\n }\\n \\n // ✅ Fix: Calculate unrealized PnL (don't divide by entryPrice)\\n let unrealizedPnl = 0;\\n model.positions.forEach(pos => {\\n if (pos.side === 'long') {\\n unrealizedPnl += (currentPrice - pos.entryPrice) * pos.size * pos.ctVal;\\n } else {\\n unrealizedPnl += (pos.entryPrice - currentPrice) * pos.size * pos.ctVal;\\n }\\n });\\n \\n model.account.totalValue = model.account.initialBalance + model.account.realizedPnl + unrealizedPnl;\\n model.account.balance = model.account.totalValue;\\n \\n } catch (error) {\\n Log(`Error processing decision: ${error.message}`);\\n Log(`Original output: ${item.output}`);\\n }\\n});\\n\\n// Save updated models to _G\\n_G('models', models);\\n\\nreturn models;\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[464,1008],\"id\":\"43ca06e2-2047-4005-bdb9-3aaa810881e8\",\"name\":\"Result Calculation\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// Extract and format all models' trading data\\nconst inputData = $input.all()[0].json;\\n\\n// ✅ Automatically get model list from data\\nconst models = Object.keys(inputData);\\n\\n// Build result array\\nconst results = models.map(model => {\\n const data = inputData[model];\\n \\n // ✅ Safety check\\n if (!data || !data.account) {\\n Log(`Warning: Model ${model} data incomplete, skipping`);\\n return null;\\n }\\n \\n // Get latest decision\\n const latestDecision = data.decisions && data.decisions.length > 0 \\n ? data.decisions[data.decisions.length - 1] \\n : null;\\n \\n // Get current position\\n const currentPosition = data.positions && data.positions.length > 0\\n ? data.positions[0]\\n : null;\\n \\n return {\\n // Model name\\n model: model,\\n \\n // Account info\\n initialBalance: data.account.initialBalance,\\n balance: data.account.balance,\\n totalValue: data.account.totalValue,\\n realizedPnl: data.account.realizedPnl,\\n totalTrades: data.account.totalTrades,\\n winTrades: data.account.winTrades,\\n winRate: data.account.totalTrades > 0 \\n ? ((data.account.winTrades / data.account.totalTrades) * 100).toFixed(2) + '%'\\n : '0%',\\n \\n // Position info\\n hasPosition: currentPosition ? 'YES' : 'NO',\\n positionType: currentPosition?.type || '-',\\n positionSize: currentPosition?.size || 0,\\n entryPrice: currentPosition?.entryPrice || 0,\\n currentPrice: currentPosition?.currentPrice || 0,\\n unrealizedPnl: currentPosition?.unrealizedPnl || 0,\\n \\n // Latest decision\\n latestAction: latestDecision?.action || 'NO_DECISION',\\n latestPrice: latestDecision?.price || 0,\\n latestTimestamp: latestDecision?.timestamp || 0,\\n latestReasoning: latestDecision?.reasoning || '-',\\n \\n // Trade history stats\\n tradeHistoryCount: data.tradeHistory.length,\\n decisionsCount: data.decisions.length,\\n \\n // Add timestamp\\n recordTime: new Date().toISOString()\\n };\\n}).filter(item => item !== null); // ✅ Filter invalid data\\n\\nlet tradelog = _G('tradelog') || [];\\n\\n// Add current records for all models\\ntradelog.push({\\n timestamp: new Date().toISOString(),\\n models: results\\n});\\n\\n_G('tradelog', tradelog);\\n\\n// Return data for next node\\nreturn tradelog;\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[688,1008],\"id\":\"afd7d308-7491-4bc0-959b-71f74271452e\",\"name\":\"Trade Log Save\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// Get all model data\\nconst inputData = $node['Result Calculation'].json;\\n\\n// Get currently active model for real trading (if any)\\nlet activeModel = _G('activeModel') || null;\\nlet highestPnl = _G('highestPnl') || 15; // Initial threshold\\n\\nLog('highestPnl:', highestPnl)\\n\\n// Iterate through all models to find best performing model\\nlet bestModel = null;\\nlet bestPnl = highestPnl;\\n\\nObject.keys(inputData).forEach(modelName => {\\n const model = inputData[modelName];\\n const realizedPnl = model.account.realizedPnl;\\n \\n // Find model with highest PnL\\n if (realizedPnl > bestPnl) {\\n bestPnl = realizedPnl;\\n bestModel = modelName;\\n }\\n});\\n\\n// Decision logic\\nlet shouldTrade = false;\\nlet tradeAction = null;\\nlet modelChanged = false;\\n\\nif (bestModel) {\\n // If no active model and found profitable model\\n if (!activeModel) {\\n activeModel = bestModel;\\n highestPnl = bestPnl;\\n modelChanged = true;\\n Log(`🎯 Start real trading! Selected model: ${activeModel}, PnL: ${bestPnl.toFixed(2)} USDT`);\\n } \\n // If current model is not the best, switch to best model\\n else if (bestModel !== activeModel) {\\n Log(`🔄 Model switch: ${activeModel} (${highestPnl.toFixed(2)}) -> ${bestModel} (${bestPnl.toFixed(2)})`);\\n activeModel = bestModel;\\n highestPnl = bestPnl;\\n modelChanged = true;\\n }\\n \\n // Get paper position status of active model\\n const activeModelData = inputData[activeModel];\\n const paperPositions = activeModelData.positions || [];\\n \\n // Get real position status\\n const realPositions = exchange.GetPosition();\\n const hasRealLong = realPositions && realPositions.some(p => p.Type === PD_LONG && p.Amount > 0);\\n const hasRealShort = realPositions && realPositions.some(p => p.Type === PD_SHORT && p.Amount > 0);\\n \\n // Paper position status\\n const hasPaperLong = paperPositions.some(p => p.side === 'long');\\n const hasPaperShort = paperPositions.some(p => p.side === 'short');\\n \\n // Sync logic: make real position match paper position\\n if (hasPaperLong && !hasRealLong) {\\n // Paper has long, real doesn't have long\\n if (hasRealShort) {\\n // If real has short, close short first\\n shouldTrade = true;\\n tradeAction = {\\n model: activeModel,\\n action: 'CLOSE_SHORT',\\n reason: 'Sync position: close short first',\\n paperPosition: paperPositions[0],\\n modelPnl: activeModelData.account.realizedPnl\\n };\\n } else {\\n // Real has no position, open long\\n shouldTrade = true;\\n tradeAction = {\\n model: activeModel,\\n action: 'OPEN_LONG',\\n reason: 'Sync position: open long',\\n paperPosition: paperPositions[0],\\n modelPnl: activeModelData.account.realizedPnl\\n };\\n }\\n } \\n else if (hasPaperShort && !hasRealShort) {\\n // Paper has short, real doesn't have short\\n if (hasRealLong) {\\n // If real has long, close long first\\n shouldTrade = true;\\n tradeAction = {\\n model: activeModel,\\n action: 'CLOSE_LONG',\\n reason: 'Sync position: close long first',\\n paperPosition: paperPositions[0],\\n modelPnl: activeModelData.account.realizedPnl\\n };\\n } else {\\n // Real has no position, open short\\n shouldTrade = true;\\n tradeAction = {\\n model: activeModel,\\n action: 'OPEN_SHORT',\\n reason: 'Sync position: open short',\\n paperPosition: paperPositions[0],\\n modelPnl: activeModelData.account.realizedPnl\\n };\\n }\\n }\\n else if (!hasPaperLong && !hasPaperShort && (hasRealLong || hasRealShort)) {\\n // Paper has no position but real has position, need to close\\n if (hasRealLong) {\\n shouldTrade = true;\\n tradeAction = {\\n model: activeModel,\\n action: 'CLOSE_LONG',\\n reason: 'Sync position: paper already closed',\\n modelPnl: activeModelData.account.realizedPnl\\n };\\n } else if (hasRealShort) {\\n shouldTrade = true;\\n tradeAction = {\\n model: activeModel,\\n action: 'CLOSE_SHORT',\\n reason: 'Sync position: paper already closed',\\n modelPnl: activeModelData.account.realizedPnl\\n };\\n }\\n }\\n}\\n\\n// Save state\\n_G('activeModel', activeModel);\\n_G('highestPnl', highestPnl);\\n\\n// Execute real trade\\nif (shouldTrade && tradeAction) {\\n const symbol = $vars.coin + \\\"_USDT.swap\\\";\\n const size = $vars.size || 0.01;\\n \\n Log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);\\n Log(`🤖 Real trade execution (sync paper position)`);\\n Log(`Model: ${tradeAction.model}`);\\n Log(`Action: ${tradeAction.action}`);\\n Log(`Reason: ${tradeAction.reason}`);\\n Log(`Size: ${size}`);\\n Log(`Model PnL: ${tradeAction.modelPnl.toFixed(2)} USDT`);\\n if (tradeAction.paperPosition) {\\n Log(`Paper position: ${tradeAction.paperPosition.side}, entry price: ${tradeAction.paperPosition.entryPrice}`);\\n }\\n Log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);\\n \\n try {\\n // Get current position\\n const positions = exchange.GetPosition();\\n const hasLongPosition = positions && positions.some(p => p.Type === PD_LONG && p.Amount > 0);\\n const hasShortPosition = positions && positions.some(p => p.Type === PD_SHORT && p.Amount > 0);\\n \\n // Execute trade\\n if (tradeAction.action === 'OPEN_LONG' && !hasLongPosition && !hasShortPosition) {\\n exchange.SetDirection(\\\"buy\\\");\\n const orderId = exchange.Buy(-1, size);\\n Log(`✅ Open long success, order ID: ${orderId}`);\\n } \\n else if (tradeAction.action === 'OPEN_SHORT' && !hasLongPosition && !hasShortPosition) {\\n exchange.SetDirection(\\\"sell\\\");\\n const orderId = exchange.Sell(-1, size);\\n Log(`✅ Open short success, order ID: ${orderId}`);\\n }\\n else if (tradeAction.action === 'CLOSE_LONG' && hasLongPosition) {\\n const longPos = positions.find(p => p.Type === PD_LONG && p.Amount > 0);\\n exchange.SetDirection(\\\"closebuy\\\");\\n const orderId = exchange.Sell(-1, longPos.Amount);\\n Log(`✅ Close long success, order ID: ${orderId}`);\\n }\\n else if (tradeAction.action === 'CLOSE_SHORT' && hasShortPosition) {\\n const shortPos = positions.find(p => p.Type === PD_SHORT && p.Amount > 0);\\n exchange.SetDirection(\\\"closesell\\\");\\n const orderId = exchange.Buy(-1, shortPos.Amount);\\n Log(`✅ Close short success, order ID: ${orderId}`);\\n }\\n else {\\n Log(`⚠️ Cannot execute trade: position status mismatch`);\\n }\\n } catch (error) {\\n Log(`❌ Trade execution failed: ${error.message}`);\\n }\\n}\\n\\n// Return status info\\nreturn [{\\n json: {\\n activeModel: activeModel,\\n highestPnl: highestPnl,\\n shouldTrade: shouldTrade,\\n modelChanged: modelChanged,\\n tradeAction: tradeAction,\\n allModelsStatus: Object.keys(inputData).map(modelName => {\\n const model = inputData[modelName];\\n return {\\n model: modelName,\\n realizedPnl: model.account.realizedPnl,\\n totalValue: model.account.totalValue,\\n isActive: modelName === activeModel,\\n paperPosition: model.positions.length > 0 ? model.positions[0].side : 'none'\\n };\\n }).sort((a, b) => b.realizedPnl - a.realizedPnl)\\n }\\n}];\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[912,912],\"id\":\"0c0712b8-3144-4516-855f-3b30876b7f0f\",\"name\":\"Best Trade\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"api_base = \\\"https://testnet.binancefuture.com\\\"\\nexchange.SetBase(api_base)\\n\\n// Get and split model list\\nconst modelListStr = $vars.modelList || '';\\nconst modelList = modelListStr.split(',').filter(name => name.trim());\\n\\nif (modelList.length === 0) {\\n return {};\\n}\\n\\n// Initialize global container\\nif (!_G('models')) {\\n _G('models', {});\\n _G('tradelog')\\n\\n let allMarkets = null;\\n for (let i = 0; i < 5; i++) {\\n allMarkets = exchange.GetMarkets();\\n if (allMarkets && Object.keys(allMarkets).length > 0) {\\n break;\\n }\\n Sleep(1000);\\n }\\n \\n const symbol = $vars.coin + '_USDT.swap';\\n _G('markets', allMarkets[symbol]);\\n Log(_G('markets'))\\n \\n const initAccount = exchange.GetAccount();\\n _G('initmoney', initAccount.Balance);\\n}\\n\\n// Create trading system for each model\\nmodelList.forEach(modelName => {\\n const key = modelName.trim();\\n const models = _G('models');\\n \\n if (!models[key]) {\\n models[key] = {\\n account: {\\n initialBalance: 10000, // Add initial capital\\n balance: 10000,\\n totalValue: 10000,\\n realizedPnl: 0,\\n totalTrades: 0,\\n winTrades: 0\\n },\\n positions: [],\\n tradeHistory: [],\\n decisions: []\\n };\\n _G('models', models);\\n }\\n});\\n\\nreturn _G('models');\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-608,1008],\"id\":\"33ff8eab-dc47-4794-8667-55e0ea29db25\",\"name\":\"Strategy Init\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== AI Model Competition Visualization Dashboard ==========\\n\\n/**\\n * Safe formatting helper functions\\n */\\nfunction safeFormat(value, decimals = 2, prefix = '') {\\n if (value === null || value === undefined || isNaN(value)) {\\n return prefix + '0';\\n }\\n return prefix + _N(value, decimals);\\n}\\n\\nfunction safePercentage(numerator, denominator, decimals = 1) {\\n if (!denominator || denominator === 0) return '0%';\\n const result = (numerator / denominator) * 100;\\n return safeFormat(result, decimals) + '%';\\n}\\n\\n/**\\n * Table 1: Model Running Status\\n */\\nfunction createModelStatusTable(allModelsStatus, models) {\\n const statusTable = {\\n type: \\\"table\\\",\\n title: \\\"🤖 AI Model Real-time Status\\\",\\n cols: [\\\"Model\\\", \\\"Active\\\", \\\"Initial\\\", \\\"Current\\\", \\\"Realized\\\", \\\"Profit%\\\", \\\"Paper Pos\\\", \\\"Trades\\\", \\\"Win%\\\", \\\"Latest Decision\\\", \\\"Decision Price\\\", \\\"Reasoning\\\"],\\n rows: []\\n };\\n \\n try {\\n allModelsStatus.forEach(model => {\\n const modelData = models[model.model];\\n if (!modelData) {\\n Log(`Warning: Model data not found ${model.model}`);\\n return;\\n }\\n \\n const latestDecision = modelData.decisions && modelData.decisions.length > 0\\n ? modelData.decisions[modelData.decisions.length - 1]\\n : null;\\n \\n const profitRate = ((modelData.account.realizedPnl || 0) / (modelData.account.initialBalance || 1)) * 100;\\n \\n // Profit rate display\\n let profitRateDisplay = \\\"\\\";\\n if (profitRate > 5) {\\n profitRateDisplay = `🔥 +${safeFormat(profitRate)}%`;\\n } else if (profitRate > 0) {\\n profitRateDisplay = `🟢 +${safeFormat(profitRate)}%`;\\n } else if (profitRate < 0) {\\n profitRateDisplay = `🔴 ${safeFormat(profitRate)}%`;\\n } else {\\n profitRateDisplay = `⚪ 0%`;\\n }\\n \\n // Position display\\n let positionDisplay = \\\"\\\";\\n if (model.paperPosition === 'none') {\\n positionDisplay = \\\"📭 Empty\\\";\\n } else if (model.paperPosition === 'long') {\\n positionDisplay = \\\"📈 Long\\\";\\n } else {\\n positionDisplay = \\\"📉 Short\\\";\\n }\\n \\n // Decision display\\n let actionDisplay = latestDecision?.action || \\\"None\\\";\\n switch(actionDisplay) {\\n case 'OPEN_LONG':\\n actionDisplay = \\\"🟢 Open Long\\\";\\n break;\\n case 'OPEN_SHORT':\\n actionDisplay = \\\"🔴 Open Short\\\";\\n break;\\n case 'CLOSE_LONG':\\n actionDisplay = \\\"⏹️ Close Long\\\";\\n break;\\n case 'CLOSE_SHORT':\\n actionDisplay = \\\"⏹️ Close Short\\\";\\n break;\\n case 'NO_ACTION':\\n actionDisplay = \\\"⏸️ Wait\\\";\\n break;\\n }\\n \\n const realizedPnl = modelData.account.realizedPnl || 0;\\n let pnlDisplay = \\\"\\\";\\n if (realizedPnl > 0) {\\n pnlDisplay = `🟢 ${safeFormat(realizedPnl, 2, \\\"$\\\")}`;\\n } else if (realizedPnl < 0) {\\n pnlDisplay = `🔴 ${safeFormat(realizedPnl, 2, \\\"$\\\")}`;\\n } else {\\n pnlDisplay = `⚪ $0`;\\n }\\n \\n statusTable.rows.push([\\n `${model.isActive ? '⭐' : '💎'} ${model.model}`,\\n model.isActive ? \\\"✅\\\" : \\\"❌\\\",\\n safeFormat(modelData.account.initialBalance, 2, \\\"$\\\"),\\n safeFormat(modelData.account.totalValue, 2, \\\"$\\\"),\\n pnlDisplay,\\n profitRateDisplay,\\n positionDisplay,\\n modelData.account.totalTrades || 0,\\n safePercentage(modelData.account.winTrades || 0, modelData.account.totalTrades || 1),\\n actionDisplay,\\n latestDecision?.price ? safeFormat(latestDecision.price, 2, \\\"$\\\") : '-',\\n latestDecision?.reasoning ? latestDecision.reasoning : '-'\\n ]);\\n });\\n \\n } catch (e) {\\n Log(`❌ Create model status table failed: ${e.message}`);\\n Log(`Error stack: ${e.stack}`);\\n statusTable.rows.push([\\n \\\"❌ Error\\\", \\\"Failed to get data\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", e.message\\n ]);\\n }\\n \\n return statusTable;\\n}\\n\\n/**\\n * Table 2: Model Performance Comparison\\n */\\nfunction createPerformanceComparisonTable(allModelsStatus, models) {\\n const perfTable = {\\n type: \\\"table\\\",\\n title: \\\"📊 AI Model Performance Comparison (Sorted by PnL)\\\",\\n cols: [\\\"Rank\\\", \\\"Model\\\", \\\"Realized\\\", \\\"Total Assets\\\", \\\"Max Profit\\\", \\\"Max Loss\\\", \\\"Max DD\\\", \\\"Sharpe\\\", \\\"Profit/Loss\\\", \\\"Trades\\\", \\\"Win%\\\", \\\"Avg Win\\\", \\\"Avg Loss\\\"],\\n rows: []\\n };\\n \\n try {\\n const performanceData = allModelsStatus.map((model, index) => {\\n const modelData = models[model.model];\\n if (!modelData) {\\n return null;\\n }\\n \\n const tradeHistory = modelData.tradeHistory || [];\\n \\n // Filter closed trades (records with pnl)\\n const closedTrades = tradeHistory.filter(trade => \\n trade.pnl !== undefined && trade.pnl !== null\\n );\\n \\n // Calculate max profit\\n const maxProfit = closedTrades.length > 0\\n ? Math.max(...closedTrades.map(t => t.pnl || 0))\\n : 0;\\n \\n // Calculate max loss\\n const maxLoss = closedTrades.length > 0\\n ? Math.min(...closedTrades.map(t => t.pnl || 0))\\n : 0;\\n \\n // Calculate max drawdown\\n let maxDrawdown = 0;\\n let peak = modelData.account.initialBalance || 10000;\\n let cumulativePnl = 0;\\n \\n closedTrades.forEach(trade => {\\n cumulativePnl += (trade.pnl || 0);\\n const currentValue = (modelData.account.initialBalance || 10000) + cumulativePnl;\\n \\n if (currentValue > peak) {\\n peak = currentValue;\\n }\\n \\n const drawdown = peak > 0 ? ((peak - currentValue) / peak) * 100 : 0;\\n if (drawdown > maxDrawdown) {\\n maxDrawdown = drawdown;\\n }\\n });\\n \\n // Calculate Sharpe ratio\\n let sharpeRatio = 0;\\n if (closedTrades.length > 1) {\\n const initialBalance = modelData.account.initialBalance || 10000;\\n const returns = closedTrades.map(t => (t.pnl || 0) / initialBalance);\\n const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;\\n \\n const variance = returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length;\\n const stdDev = Math.sqrt(variance);\\n \\n sharpeRatio = stdDev !== 0 ? (avgReturn / stdDev) : 0;\\n }\\n \\n // Profit/Loss ratio\\n const winTrades = closedTrades.filter(t => (t.pnl || 0) > 0);\\n const lossTrades = closedTrades.filter(t => (t.pnl || 0) < 0);\\n \\n const avgWin = winTrades.length > 0\\n ? winTrades.reduce((sum, t) => sum + (t.pnl || 0), 0) / winTrades.length\\n : 0;\\n \\n const avgLoss = lossTrades.length > 0\\n ? Math.abs(lossTrades.reduce((sum, t) => sum + (t.pnl || 0), 0) / lossTrades.length)\\n : 0;\\n \\n const profitLossRatio = avgLoss !== 0 ? (avgWin / avgLoss) : 0;\\n \\n return {\\n model: model.model,\\n rank: index + 1,\\n realizedPnl: modelData.account.realizedPnl || 0,\\n totalValue: modelData.account.totalValue || 0,\\n maxProfit: maxProfit,\\n maxLoss: maxLoss,\\n maxDrawdown: maxDrawdown,\\n sharpeRatio: sharpeRatio,\\n profitLossRatio: profitLossRatio,\\n totalTrades: closedTrades.length,\\n winRate: (modelData.account.totalTrades || 0) > 0 \\n ? (((modelData.account.winTrades || 0) / modelData.account.totalTrades) * 100)\\n : 0,\\n avgWin: avgWin,\\n avgLoss: avgLoss\\n };\\n }).filter(perf => perf !== null).sort((a, b) => b.realizedPnl - a.realizedPnl);\\n \\n performanceData.forEach((perf, index) => {\\n // Rank display\\n let rankDisplay = \\\"\\\";\\n if (index === 0) {\\n rankDisplay = \\\"🥇\\\";\\n } else if (index === 1) {\\n rankDisplay = \\\"🥈\\\";\\n } else if (index === 2) {\\n rankDisplay = \\\"🥉\\\";\\n } else {\\n rankDisplay = `${index + 1}`;\\n }\\n \\n // Sharpe ratio display\\n let sharpeDisplay = \\\"\\\";\\n if (perf.sharpeRatio > 2) {\\n sharpeDisplay = `🔥 ${safeFormat(perf.sharpeRatio)}`;\\n } else if (perf.sharpeRatio > 1) {\\n sharpeDisplay = `✅ ${safeFormat(perf.sharpeRatio)}`;\\n } else if (perf.sharpeRatio > 0) {\\n sharpeDisplay = `⚡ ${safeFormat(perf.sharpeRatio)}`;\\n } else {\\n sharpeDisplay = `⚠️ ${safeFormat(perf.sharpeRatio)}`;\\n }\\n \\n perfTable.rows.push([\\n rankDisplay,\\n `💎 ${perf.model}`,\\n perf.realizedPnl > 0 \\n ? `🟢 ${safeFormat(perf.realizedPnl, 2, \\\"$\\\")}`\\n : perf.realizedPnl < 0\\n ? `🔴 ${safeFormat(perf.realizedPnl, 2, \\\"$\\\")}`\\n : `⚪ $0`,\\n safeFormat(perf.totalValue, 2, \\\"$\\\"),\\n perf.maxProfit > 0 ? `🟢 ${safeFormat(perf.maxProfit, 2, \\\"$\\\")}` : '$0',\\n perf.maxLoss < 0 ? `🔴 ${safeFormat(perf.maxLoss, 2, \\\"$\\\")}` : '$0',\\n perf.maxDrawdown > 0 ? `⚠️ ${safeFormat(perf.maxDrawdown)}%` : '0%',\\n sharpeDisplay,\\n perf.profitLossRatio > 0 ? safeFormat(perf.profitLossRatio) : '0',\\n perf.totalTrades,\\n `${safeFormat(perf.winRate, 1)}%`,\\n perf.avgWin > 0 ? safeFormat(perf.avgWin, 2, \\\"$\\\") : '$0',\\n perf.avgLoss > 0 ? safeFormat(perf.avgLoss, 2, \\\"$\\\") : '$0'\\n ]);\\n });\\n \\n // Add summary row\\n if (performanceData.length > 0) {\\n const avgPnl = performanceData.reduce((sum, p) => sum + p.realizedPnl, 0) / performanceData.length;\\n const avgSharpe = performanceData.reduce((sum, p) => sum + p.sharpeRatio, 0) / performanceData.length;\\n const totalTrades = performanceData.reduce((sum, p) => sum + p.totalTrades, 0);\\n \\n perfTable.rows.push([\\n \\\"📊\\\",\\n \\\"Average\\\",\\n avgPnl > 0 ? `🟢 ${safeFormat(avgPnl, 2, \\\"$\\\")}` : avgPnl < 0 ? `🔴 ${safeFormat(avgPnl, 2, \\\"$\\\")}` : `⚪ $0`,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\",\\n safeFormat(avgSharpe),\\n \\\"-\\\",\\n totalTrades,\\n \\\"-\\\",\\n \\\"-\\\",\\n \\\"-\\\"\\n ]);\\n }\\n \\n } catch (e) {\\n Log(`❌ Create performance comparison table failed: ${e.message}`);\\n Log(`Error stack: ${e.stack}`);\\n perfTable.rows.push([\\n \\\"❌\\\", \\\"Error\\\", \\\"Failed to get data\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", \\\"-\\\", e.message\\n ]);\\n }\\n \\n return perfTable;\\n}\\n\\n/**\\n * Table 3: Real Trading Status\\n */\\nfunction createRealTradingTable(inputData, models) {\\n const realTable = {\\n type: \\\"table\\\",\\n title: \\\"💼 Real Trading Status\\\",\\n cols: [\\\"Item\\\", \\\"Value\\\", \\\"Status\\\", \\\"Note\\\"],\\n rows: []\\n };\\n \\n try {\\n const activeModel = inputData.activeModel;\\n const realAccount = exchange.GetAccount();\\n const realPositions = exchange.GetPosition();\\n \\n // Get real position info\\n let realPositionInfo = {\\n type: 'None',\\n size: 0,\\n entryPrice: 0,\\n currentPrice: 0,\\n unrealizedPnl: 0\\n };\\n \\n if (realPositions && realPositions.length > 0) {\\n const pos = realPositions[0];\\n const ticker = exchange.GetTicker();\\n const currentPrice = ticker ? ticker.Last : 0;\\n \\n realPositionInfo = {\\n type: pos.Type === PD_LONG ? 'Long' : 'Short',\\n size: pos.Amount || 0,\\n entryPrice: pos.Price || 0,\\n currentPrice: currentPrice,\\n unrealizedPnl: pos.Profit || 0\\n };\\n }\\n \\n // Calculate real account cumulative profit\\n const initMoney = _G('initmoney') || 10000;\\n const currentBalance = realAccount ? (realAccount.Balance || 0) : 0;\\n const currentEquity = realAccount ? (realAccount.Equity || 0) : 0;\\n const realizedProfit = currentBalance - initMoney;\\n const totalProfit = currentEquity - initMoney;\\n const returnRate = initMoney > 0 ? ((totalProfit / initMoney) * 100) : 0;\\n\\n LogProfit(totalProfit, \\\"&\\\");\\n \\n // Active model info\\n realTable.rows.push([\\n \\\"🤖 Active Model\\\",\\n activeModel || \\\"Not Started\\\",\\n activeModel ? \\\"✅ Running\\\" : \\\"⏸️ Standby\\\",\\n activeModel && models[activeModel] \\n ? `PnL: ${safeFormat(models[activeModel].account.realizedPnl, 2, \\\"$\\\")}` \\n : \\\"Wait for profit > threshold to trigger\\\"\\n ]);\\n \\n // Account info\\n realTable.rows.push([\\n \\\"💰 Initial Capital\\\",\\n safeFormat(initMoney, 2, \\\"$\\\"),\\n \\\"📊\\\",\\n \\\"Real account start amount\\\"\\n ]);\\n \\n realTable.rows.push([\\n \\\"💵 Current Balance\\\",\\n safeFormat(currentBalance, 2, \\\"$\\\"),\\n \\\"📊\\\",\\n \\\"Available funds\\\"\\n ]);\\n \\n realTable.rows.push([\\n \\\"💎 Account Equity\\\",\\n safeFormat(currentEquity, 2, \\\"$\\\"),\\n \\\"📊\\\",\\n \\\"Balance + Position PnL\\\"\\n ]);\\n \\n // PnL info\\n let profitDisplay = \\\"\\\";\\n let profitStatus = \\\"\\\";\\n if (totalProfit > 0) {\\n profitDisplay = `🟢 +${safeFormat(totalProfit, 2, \\\"$\\\")}`;\\n profitStatus = \\\"✅ Profitable\\\";\\n } else if (totalProfit < 0) {\\n profitDisplay = `🔴 ${safeFormat(totalProfit, 2, \\\"$\\\")}`;\\n profitStatus = \\\"⚠️ Loss\\\";\\n } else {\\n profitDisplay = `⚪ $0`;\\n profitStatus = \\\"⚪ Breakeven\\\";\\n }\\n \\n realTable.rows.push([\\n \\\"📈 Total PnL\\\",\\n profitDisplay,\\n profitStatus,\\n `Return: ${returnRate > 0 ? '+' : ''}${safeFormat(returnRate)}%`\\n ]);\\n \\n // Position info\\n realTable.rows.push([\\n \\\"💼 Current Position\\\",\\n realPositionInfo.type === 'None' ? \\\"📭 Empty\\\" : `${realPositionInfo.type === 'Long' ? '📈' : '📉'} ${realPositionInfo.type}`,\\n realPositionInfo.type === 'None' ? \\\"⚪ No Position\\\" : \\\"✅ In Position\\\",\\n realPositionInfo.type === 'None' ? \\\"-\\\" : `Size: ${safeFormat(realPositionInfo.size, 4)}`\\n ]);\\n \\n if (realPositionInfo.type !== 'None') {\\n realTable.rows.push([\\n \\\"💰 Entry Price\\\",\\n safeFormat(realPositionInfo.entryPrice, 4, \\\"$\\\"),\\n \\\"📊\\\",\\n `Current: ${safeFormat(realPositionInfo.currentPrice, 4, \\\"$\\\")}`\\n ]);\\n \\n let positionPnlDisplay = \\\"\\\";\\n let positionPnlStatus = \\\"\\\";\\n if (realPositionInfo.unrealizedPnl > 0) {\\n positionPnlDisplay = `🟢 +${safeFormat(realPositionInfo.unrealizedPnl, 2, \\\"$\\\")}`;\\n positionPnlStatus = \\\"✅ Floating Profit\\\";\\n } else if (realPositionInfo.unrealizedPnl < 0) {\\n positionPnlDisplay = `🔴 ${safeFormat(realPositionInfo.unrealizedPnl, 2, \\\"$\\\")}`;\\n positionPnlStatus = \\\"⚠️ Floating Loss\\\";\\n } else {\\n positionPnlDisplay = `⚪ $0`;\\n positionPnlStatus = \\\"⚪ Breakeven\\\";\\n }\\n \\n realTable.rows.push([\\n \\\"📊 Position PnL\\\",\\n positionPnlDisplay,\\n positionPnlStatus,\\n \\\"Unrealized PnL\\\"\\n ]);\\n }\\n \\n // Sync status\\n realTable.rows.push([\\n \\\"🔄 Sync Status\\\",\\n inputData.shouldTrade ? \\\"Pending Sync\\\" : \\\"Synced\\\",\\n inputData.shouldTrade ? \\\"🔄 Executing\\\" : \\\"✅ Synced\\\",\\n inputData.tradeAction?.reason || \\\"Paper and real match\\\"\\n ]);\\n \\n // Last trade\\n if (inputData.tradeAction) {\\n let actionDisplay = inputData.tradeAction.action;\\n switch(actionDisplay) {\\n case 'OPEN_LONG':\\n actionDisplay = \\\"🟢 Open Long\\\";\\n break;\\n case 'OPEN_SHORT':\\n actionDisplay = \\\"🔴 Open Short\\\";\\n break;\\n case 'CLOSE_LONG':\\n actionDisplay = \\\"⏹️ Close Long\\\";\\n break;\\n case 'CLOSE_SHORT':\\n actionDisplay = \\\"⏹️ Close Short\\\";\\n break;\\n }\\n \\n realTable.rows.push([\\n \\\"📝 Last Trade\\\",\\n actionDisplay,\\n \\\"📊\\\",\\n inputData.tradeAction.reason || \\\"-\\\"\\n ]);\\n }\\n \\n // Model comparison\\n if (activeModel && models[activeModel]) {\\n const modelPnl = models[activeModel].account.realizedPnl || 0;\\n const gap = realizedProfit - modelPnl;\\n \\n realTable.rows.push([\\n \\\"📊 Gap vs Model\\\",\\n gap > 0 ? `🟢 Ahead ${safeFormat(gap, 2, \\\"$\\\")}` : gap < 0 ? `🔴 Behind ${safeFormat(Math.abs(gap), 2, \\\"$\\\")}` : `⚪ Match`,\\n gap > 0 ? \\\"✅ Leading\\\" : gap < 0 ? \\\"⚠️ Catching Up\\\" : \\\"⚪ Synced\\\",\\n `Model Paper: ${safeFormat(modelPnl, 2, \\\"$\\\")}`\\n ]);\\n }\\n \\n } catch (e) {\\n Log(`❌ Create real trading table failed: ${e.message}`);\\n Log(`Error stack: ${e.stack}`);\\n realTable.rows.push([\\n \\\"❌ Error\\\",\\n \\\"Failed to get data\\\",\\n \\\"🚨\\\",\\n e.message\\n ]);\\n }\\n \\n return realTable;\\n}\\n\\n/**\\n * Table 4: System Statistics\\n */\\nfunction createSystemStatsTable(allModelsStatus, models, inputData) {\\n const statsTable = {\\n type: \\\"table\\\",\\n title: \\\"📊 System Overall Statistics\\\",\\n cols: [\\\"Metric\\\", \\\"Value\\\", \\\"Rating\\\", \\\"Note\\\"],\\n rows: []\\n };\\n \\n try {\\n // Total models\\n statsTable.rows.push([\\n \\\"🤖 Competing Models\\\",\\n `${allModelsStatus.length} models`,\\n \\\"📊\\\",\\n \\\"AI Model Competition\\\"\\n ]);\\n \\n // Best model\\n if (allModelsStatus.length > 0) {\\n const bestModel = allModelsStatus[0];\\n statsTable.rows.push([\\n \\\"🥇 Best Model\\\",\\n bestModel.model,\\n bestModel.realizedPnl > 0 ? \\\"✅ Profitable\\\" : \\\"⚠️ Loss\\\",\\n `PnL: ${safeFormat(bestModel.realizedPnl, 2, \\\"$\\\")}`\\n ]);\\n }\\n \\n // Average performance\\n if (allModelsStatus.length > 0) {\\n const avgPnl = allModelsStatus.reduce((sum, m) => sum + (m.realizedPnl || 0), 0) / allModelsStatus.length;\\n statsTable.rows.push([\\n \\\"📊 Average PnL\\\",\\n avgPnl > 0 ? `🟢 ${safeFormat(avgPnl, 2, \\\"$\\\")}` : avgPnl < 0 ? `🔴 ${safeFormat(avgPnl, 2, \\\"$\\\")}` : `⚪ $0`,\\n avgPnl > 0 ? \\\"✅ Positive\\\" : \\\"⚠️ Negative\\\",\\n \\\"All models average\\\"\\n ]);\\n }\\n \\n // Profitable models count\\n const profitableModels = allModelsStatus.filter(m => (m.realizedPnl || 0) > 0).length;\\n statsTable.rows.push([\\n \\\"✅ Profitable Models\\\",\\n `${profitableModels} / ${allModelsStatus.length}`,\\n profitableModels > allModelsStatus.length / 2 ? \\\"✅ Good\\\" : \\\"⚠️ Low\\\",\\n `Ratio: ${safePercentage(profitableModels, allModelsStatus.length)}`\\n ]);\\n \\n // Total trades\\n const totalTrades = allModelsStatus.reduce((sum, m) => {\\n const modelData = models[m.model];\\n return sum + (modelData ? (modelData.account.totalTrades || 0) : 0);\\n }, 0);\\n \\n statsTable.rows.push([\\n \\\"🎲 Total Trades\\\",\\n totalTrades,\\n totalTrades > 50 ? \\\"✅ Sufficient\\\" : totalTrades > 10 ? \\\"✅ Moderate\\\" : \\\"⚠️ Low\\\",\\n `All models combined`\\n ]);\\n \\n // Average win rate\\n let totalWinRate = 0;\\n let validModels = 0;\\n allModelsStatus.forEach(m => {\\n const modelData = models[m.model];\\n if (modelData && (modelData.account.totalTrades || 0) > 0) {\\n totalWinRate += ((modelData.account.winTrades || 0) / modelData.account.totalTrades) * 100;\\n validModels++;\\n }\\n });\\n const avgWinRate = validModels > 0 ? totalWinRate / validModels : 0;\\n \\n statsTable.rows.push([\\n \\\"🎯 Average Win Rate\\\",\\n `${safeFormat(avgWinRate, 1)}%`,\\n avgWinRate > 60 ? \\\"🔥 Excellent\\\" : avgWinRate > 45 ? \\\"✅ Good\\\" : \\\"⚠️ Need Improvement\\\",\\n \\\"Valid trading models average\\\"\\n ]);\\n \\n // Models with position\\n const modelsWithPosition = allModelsStatus.filter(m => m.paperPosition !== 'none').length;\\n statsTable.rows.push([\\n \\\"💼 Models in Position\\\",\\n `${modelsWithPosition} / ${allModelsStatus.length}`,\\n modelsWithPosition > 0 ? \\\"✅ Active\\\" : \\\"📭 Empty\\\",\\n `Ratio: ${safePercentage(modelsWithPosition, allModelsStatus.length)}`\\n ]);\\n \\n // Real trading status\\n const isRealTrading = inputData.activeModel ? true : false;\\n statsTable.rows.push([\\n \\\"🚀 Real Trading Status\\\",\\n isRealTrading ? \\\"Running\\\" : \\\"Not Started\\\",\\n isRealTrading ? \\\"✅ Active\\\" : \\\"⏸️ Standby\\\",\\n isRealTrading ? `Following: ${inputData.activeModel}` : \\\"Wait for profit>500\\\"\\n ]);\\n \\n // Last update time\\n const updateTime = new Date().toLocaleString();\\n statsTable.rows.push([\\n \\\"🕐 Update Time\\\",\\n updateTime,\\n \\\"📊\\\",\\n \\\"Real-time monitoring\\\"\\n ]);\\n \\n } catch (e) {\\n Log(`❌ Create stats table failed: ${e.message}`);\\n Log(`Error stack: ${e.stack}`);\\n statsTable.rows.push([\\n \\\"❌ Error\\\",\\n \\\"Calculation failed\\\",\\n \\\"🚨\\\",\\n e.message\\n ]);\\n }\\n \\n return statsTable;\\n}\\n\\n/**\\n * Main function - Display complete dashboard\\n */\\nfunction main() {\\n try {\\n Log('Visualization node execution started');\\n \\n // Get input data\\n const inputData = $input.all()[0].json;\\n Log('inputData keys:', Object.keys(inputData));\\n \\n const allModelsStatus = inputData.allModelsStatus;\\n if (!allModelsStatus || !Array.isArray(allModelsStatus)) {\\n throw new Error('allModelsStatus data invalid or not an array');\\n }\\n \\n Log(`Model count: ${allModelsStatus.length}`);\\n \\n // Get detailed model data\\n const models = $node['Result Calculation'].json;\\n if (!models) {\\n throw new Error('Unable to get Result Calculation node data');\\n }\\n \\n Log('models keys:', Object.keys(models));\\n \\n // Create 4 tables\\n const table1 = createModelStatusTable(allModelsStatus, models);\\n const table2 = createPerformanceComparisonTable(allModelsStatus, models);\\n const table3 = createRealTradingTable(inputData, models);\\n const table4 = createSystemStatsTable(allModelsStatus, models, inputData);\\n \\n // Combined display\\n const dashboardDisplay = \\n '`' + JSON.stringify(table1) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(table2) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(table3) + '`\\\\n\\\\n' +\\n '`' + JSON.stringify(table4) + '`';\\n \\n LogStatus(dashboardDisplay);\\n \\n Log('✅ Visualization display success');\\n \\n // Return complete data\\n return [{\\n json: {\\n ...inputData,\\n tables: {\\n modelStatus: table1,\\n performance: table2,\\n realTrading: table3,\\n systemStats: table4\\n },\\n timestamp: Date.now(),\\n updateTime: new Date().toLocaleString()\\n }\\n }];\\n \\n } catch (e) {\\n Log(`❌ Display dashboard failed: ${e.message}`);\\n Log(`❌ Error stack: ${e.stack}`);\\n \\n // Show simple status on error\\n const errorTable = {\\n type: \\\"table\\\",\\n title: \\\"❌ System Error\\\",\\n cols: [\\\"Error Type\\\", \\\"Error Message\\\", \\\"Time\\\"],\\n rows: [[\\n \\\"Visualization Error\\\",\\n e.message,\\n new Date().toLocaleString()\\n ]]\\n };\\n \\n LogStatus('`' + JSON.stringify(errorTable) + '`');\\n \\n return [{\\n json: {\\n error: e.message,\\n stack: e.stack,\\n timestamp: Date.now()\\n }\\n }];\\n }\\n}\\n\\n// Execute main function\\nreturn main();\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[1136,912],\"id\":\"939c1c0f-3c4d-43ab-be31-c2f1ece3b06c\",\"name\":\"Visualization\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"const symbol = $vars.coin + \\\"_USDT.swap\\\"\\nconst timeframes = {\\n day: PERIOD_D1,\\n hour: PERIOD_H1,\\n min5: PERIOD_M5\\n}\\n\\nfunction getIndicatorsForPeriod(symbol, period, periodName) {\\n const records = exchange.GetRecords(symbol, period)\\n \\n if (!records || records.length <= 10) {\\n Log(`Warning: ${periodName} period insufficient data, only ${records ? records.length : 0} records`)\\n return null\\n }\\n \\n const macd = talib.MACD(records)\\n const rsi = talib.RSI(records, 14)\\n const atr = talib.ATR(records, 14)\\n const obv = talib.OBV(records)\\n \\n function getLast10Values(arr) {\\n if (!arr || arr.length === 0) return []\\n return arr.slice(-10)\\n }\\n \\n return {\\n period: periodName,\\n recordsCount: records.length,\\n lastPrice: records[records.length - 1].Close,\\n MACD: {\\n macd: getLast10Values(macd[0]),\\n signal: getLast10Values(macd[1]), \\n histogram: getLast10Values(macd[2])\\n },\\n RSI: getLast10Values(rsi),\\n ATR: getLast10Values(atr),\\n OBV: getLast10Values(obv)\\n }\\n}\\n\\nconst models = _G('models')\\nconst modelPnlData = Object.keys(models).map(modelName => ({\\n name: modelName,\\n realizedPnl: models[modelName].account.realizedPnl,\\n totalValue: models[modelName].account.totalValue\\n}))\\n\\nconst sortedModels = modelPnlData.sort((a, b) => b.realizedPnl - a.realizedPnl)\\n\\nconst rankedModels = []\\nlet currentRank = 1\\nlet previousPnl = null\\n\\nsortedModels.forEach((model, index) => {\\n if (previousPnl !== null && model.realizedPnl < previousPnl) {\\n currentRank = index + 1\\n }\\n \\n rankedModels.push({\\n ...model,\\n rank: currentRank\\n })\\n \\n previousPnl = model.realizedPnl\\n})\\n\\nconst currentModelName = Object.keys(models)[0] // First model\\nconst currentModelRank = rankedModels.find(model => model.name === currentModelName)\\n\\nconst rankingText = rankedModels.map(model => \\n `${model.rank}. ${model.name}: ${model.realizedPnl} USDT${model.name === currentModelName ? ' ← Your Position' : ''}`\\n).join('\\\\n')\\n\\nlet gapInfo = ''\\nif (currentModelRank.rank > 1) {\\n const firstPlace = rankedModels[0]\\n const gap = (firstPlace.realizedPnl - currentModelRank.realizedPnl).toFixed(2)\\n gapInfo = `### Gap from 1st Place\\\\n1st Place PnL: ${firstPlace.realizedPnl} USDT\\\\nYou need to catch up: ${gap} USDT`\\n} else {\\n gapInfo = '🏆 Congratulations! You are leading! Keep the advantage!'\\n}\\n\\nconst result = {\\n symbol: symbol,\\n timestamp: Date.now(),\\n timeframes: {\\n day: getIndicatorsForPeriod(symbol, timeframes.day, 'Daily'),\\n hour: getIndicatorsForPeriod(symbol, timeframes.hour, 'Hourly'),\\n min5: getIndicatorsForPeriod(symbol, timeframes.min5, '5-minute')\\n },\\n // ⚠️ Modified: Use model's own virtual position instead of real position\\n position: models[currentModelName].positions || [],\\n ranking: {\\n currentModel: currentModelName,\\n currentRank: currentModelRank.rank,\\n currentPnl: currentModelRank.realizedPnl,\\n currentTotalValue: currentModelRank.totalValue,\\n totalTrades: models[currentModelName].account.totalTrades,\\n winTrades: models[currentModelName].account.winTrades,\\n winRate: models[currentModelName].account.totalTrades > 0 ? \\n (models[currentModelName].account.winTrades / models[currentModelName].account.totalTrades * 100).toFixed(2) : '0',\\n rankingText: rankingText,\\n gapInfo: gapInfo,\\n totalModels: Object.keys(models).length,\\n allRankings: rankedModels\\n }\\n}\\n\\nreturn result\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-384,16],\"id\":\"910841e7-88cc-4ea0-9b64-c5ff2019967f\",\"name\":\"DS Processing\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"const symbol = $vars.coin + \\\"_USDT.swap\\\"\\nconst timeframes = {\\n day: PERIOD_D1,\\n hour: PERIOD_H1,\\n min5: PERIOD_M5\\n}\\n\\nfunction getIndicatorsForPeriod(symbol, period, periodName) {\\n const records = exchange.GetRecords(symbol, period)\\n \\n if (!records || records.length <= 10) {\\n Log(`Warning: ${periodName} period insufficient data, only ${records ? records.length : 0} records`)\\n return null\\n }\\n \\n const macd = talib.MACD(records)\\n const rsi = talib.RSI(records, 14)\\n const atr = talib.ATR(records, 14)\\n const obv = talib.OBV(records)\\n \\n function getLast10Values(arr) {\\n if (!arr || arr.length === 0) return []\\n return arr.slice(-10)\\n }\\n \\n return {\\n period: periodName,\\n recordsCount: records.length,\\n lastPrice: records[records.length - 1].Close,\\n MACD: {\\n macd: getLast10Values(macd[0]),\\n signal: getLast10Values(macd[1]), \\n histogram: getLast10Values(macd[2])\\n },\\n RSI: getLast10Values(rsi),\\n ATR: getLast10Values(atr),\\n OBV: getLast10Values(obv)\\n }\\n}\\n\\nconst models = _G('models')\\nconst modelPnlData = Object.keys(models).map(modelName => ({\\n name: modelName,\\n realizedPnl: models[modelName].account.realizedPnl,\\n totalValue: models[modelName].account.totalValue\\n}))\\n\\nconst sortedModels = modelPnlData.sort((a, b) => b.realizedPnl - a.realizedPnl)\\n\\nconst rankedModels = []\\nlet currentRank = 1\\nlet previousPnl = null\\n\\nsortedModels.forEach((model, index) => {\\n if (previousPnl !== null && model.realizedPnl < previousPnl) {\\n currentRank = index + 1\\n }\\n \\n rankedModels.push({\\n ...model,\\n rank: currentRank\\n })\\n \\n previousPnl = model.realizedPnl\\n})\\n\\nconst currentModelName = Object.keys(models)[1] // Second model\\nconst currentModelRank = rankedModels.find(model => model.name === currentModelName)\\n\\nconst rankingText = rankedModels.map(model => \\n `${model.rank}. ${model.name}: ${model.realizedPnl} USDT${model.name === currentModelName ? ' ← Your Position' : ''}`\\n).join('\\\\n')\\n\\nlet gapInfo = ''\\nif (currentModelRank.rank > 1) {\\n const firstPlace = rankedModels[0]\\n const gap = (firstPlace.realizedPnl - currentModelRank.realizedPnl).toFixed(2)\\n gapInfo = `### Gap from 1st Place\\\\n1st Place PnL: ${firstPlace.realizedPnl} USDT\\\\nYou need to catch up: ${gap} USDT`\\n} else {\\n gapInfo = '🏆 Congratulations! You are leading! Keep the advantage!'\\n}\\n\\nconst result = {\\n symbol: symbol,\\n timestamp: Date.now(),\\n timeframes: {\\n day: getIndicatorsForPeriod(symbol, timeframes.day, 'Daily'),\\n hour: getIndicatorsForPeriod(symbol, timeframes.hour, 'Hourly'),\\n min5: getIndicatorsForPeriod(symbol, timeframes.min5, '5-minute')\\n },\\n // ⚠️ Modified: Use model's own virtual position instead of real position\\n position: models[currentModelName].positions || [],\\n ranking: {\\n currentModel: currentModelName,\\n currentRank: currentModelRank.rank,\\n currentPnl: currentModelRank.realizedPnl,\\n currentTotalValue: currentModelRank.totalValue,\\n totalTrades: models[currentModelName].account.totalTrades,\\n winTrades: models[currentModelName].account.winTrades,\\n winRate: models[currentModelName].account.totalTrades > 0 ? \\n (models[currentModelName].account.winTrades / models[currentModelName].account.totalTrades * 100).toFixed(2) : '0',\\n rankingText: rankingText,\\n gapInfo: gapInfo,\\n totalModels: Object.keys(models).length,\\n allRankings: rankedModels\\n }\\n}\\n\\nreturn result\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-384,416],\"id\":\"4854a515-d721-4d32-b7f9-fd0b3706c36a\",\"name\":\"Claude Processing\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"const symbol = $vars.coin + \\\"_USDT.swap\\\"\\nconst timeframes = {\\n day: PERIOD_D1,\\n hour: PERIOD_H1,\\n min5: PERIOD_M5\\n}\\n\\nfunction getIndicatorsForPeriod(symbol, period, periodName) {\\n const records = exchange.GetRecords(symbol, period)\\n \\n if (!records || records.length <= 10) {\\n Log(`Warning: ${periodName} period insufficient data, only ${records ? records.length : 0} records`)\\n return null\\n }\\n \\n const macd = talib.MACD(records)\\n const rsi = talib.RSI(records, 14)\\n const atr = talib.ATR(records, 14)\\n const obv = talib.OBV(records)\\n \\n function getLast10Values(arr) {\\n if (!arr || arr.length === 0) return []\\n return arr.slice(-10)\\n }\\n \\n return {\\n period: periodName,\\n recordsCount: records.length,\\n lastPrice: records[records.length - 1].Close,\\n MACD: {\\n macd: getLast10Values(macd[0]),\\n signal: getLast10Values(macd[1]), \\n histogram: getLast10Values(macd[2])\\n },\\n RSI: getLast10Values(rsi),\\n ATR: getLast10Values(atr),\\n OBV: getLast10Values(obv)\\n }\\n}\\n\\nconst models = _G('models')\\nconst modelPnlData = Object.keys(models).map(modelName => ({\\n name: modelName,\\n realizedPnl: models[modelName].account.realizedPnl,\\n totalValue: models[modelName].account.totalValue\\n}))\\n\\nconst sortedModels = modelPnlData.sort((a, b) => b.realizedPnl - a.realizedPnl)\\n\\nconst rankedModels = []\\nlet currentRank = 1\\nlet previousPnl = null\\n\\nsortedModels.forEach((model, index) => {\\n if (previousPnl !== null && model.realizedPnl < previousPnl) {\\n currentRank = index + 1\\n }\\n \\n rankedModels.push({\\n ...model,\\n rank: currentRank\\n })\\n \\n previousPnl = model.realizedPnl\\n})\\n\\nconst currentModelName = Object.keys(models)[2] // Third model\\nconst currentModelRank = rankedModels.find(model => model.name === currentModelName)\\n\\nconst rankingText = rankedModels.map(model => \\n `${model.rank}. ${model.name}: ${model.realizedPnl} USDT${model.name === currentModelName ? ' ← Your Position' : ''}`\\n).join('\\\\n')\\n\\nlet gapInfo = ''\\nif (currentModelRank.rank > 1) {\\n const firstPlace = rankedModels[0]\\n const gap = (firstPlace.realizedPnl - currentModelRank.realizedPnl).toFixed(2)\\n gapInfo = `### Gap from 1st Place\\\\n1st Place PnL: ${firstPlace.realizedPnl} USDT\\\\nYou need to catch up: ${gap} USDT`\\n} else {\\n gapInfo = '🏆 Congratulations! You are leading! Keep the advantage!'\\n}\\n\\nconst result = {\\n symbol: symbol,\\n timestamp: Date.now(),\\n timeframes: {\\n day: getIndicatorsForPeriod(symbol, timeframes.day, 'Daily'),\\n hour: getIndicatorsForPeriod(symbol, timeframes.hour, 'Hourly'),\\n min5: getIndicatorsForPeriod(symbol, timeframes.min5, '5-minute')\\n },\\n // ⚠️ Modified: Use model's own virtual position instead of real position\\n position: models[currentModelName].positions || [],\\n ranking: {\\n currentModel: currentModelName,\\n currentRank: currentModelRank.rank,\\n currentPnl: currentModelRank.realizedPnl,\\n currentTotalValue: currentModelRank.totalValue,\\n totalTrades: models[currentModelName].account.totalTrades,\\n winTrades: models[currentModelName].account.winTrades,\\n winRate: models[currentModelName].account.totalTrades > 0 ? \\n (models[currentModelName].account.winTrades / models[currentModelName].account.totalTrades * 100).toFixed(2) : '0',\\n rankingText: rankingText,\\n gapInfo: gapInfo,\\n totalModels: Object.keys(models).length,\\n allRankings: rankedModels\\n }\\n}\\n\\nreturn result\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-384,816],\"id\":\"56aa5a7b-479c-47df-b3c7-d47ccdedae29\",\"name\":\"QWEN Processing\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"const symbol = $vars.coin + \\\"_USDT.swap\\\"\\nconst timeframes = {\\n day: PERIOD_D1,\\n hour: PERIOD_H1,\\n min5: PERIOD_M5\\n}\\n\\nfunction getIndicatorsForPeriod(symbol, period, periodName) {\\n const records = exchange.GetRecords(symbol, period)\\n \\n if (!records || records.length <= 10) {\\n Log(`Warning: ${periodName} period insufficient data, only ${records ? records.length : 0} records`)\\n return null\\n }\\n \\n const macd = talib.MACD(records)\\n const rsi = talib.RSI(records, 14)\\n const atr = talib.ATR(records, 14)\\n const obv = talib.OBV(records)\\n \\n function getLast10Values(arr) {\\n if (!arr || arr.length === 0) return []\\n return arr.slice(-10)\\n }\\n \\n return {\\n period: periodName,\\n recordsCount: records.length,\\n lastPrice: records[records.length - 1].Close,\\n MACD: {\\n macd: getLast10Values(macd[0]),\\n signal: getLast10Values(macd[1]), \\n histogram: getLast10Values(macd[2])\\n },\\n RSI: getLast10Values(rsi),\\n ATR: getLast10Values(atr),\\n OBV: getLast10Values(obv)\\n }\\n}\\n\\nconst models = _G('models')\\nconst modelPnlData = Object.keys(models).map(modelName => ({\\n name: modelName,\\n realizedPnl: models[modelName].account.realizedPnl,\\n totalValue: models[modelName].account.totalValue\\n}))\\n\\nconst sortedModels = modelPnlData.sort((a, b) => b.realizedPnl - a.realizedPnl)\\n\\nconst rankedModels = []\\nlet currentRank = 1\\nlet previousPnl = null\\n\\nsortedModels.forEach((model, index) => {\\n if (previousPnl !== null && model.realizedPnl < previousPnl) {\\n currentRank = index + 1\\n }\\n \\n rankedModels.push({\\n ...model,\\n rank: currentRank\\n })\\n \\n previousPnl = model.realizedPnl\\n})\\n\\nconst currentModelName = Object.keys(models)[3] // Fourth model\\nconst currentModelRank = rankedModels.find(model => model.name === currentModelName)\\n\\nconst rankingText = rankedModels.map(model => \\n `${model.rank}. ${model.name}: ${model.realizedPnl} USDT${model.name === currentModelName ? ' ← Your Position' : ''}`\\n).join('\\\\n')\\n\\nlet gapInfo = ''\\nif (currentModelRank.rank > 1) {\\n const firstPlace = rankedModels[0]\\n const gap = (firstPlace.realizedPnl - currentModelRank.realizedPnl).toFixed(2)\\n gapInfo = `### Gap from 1st Place\\\\n1st Place PnL: ${firstPlace.realizedPnl} USDT\\\\nYou need to catch up: ${gap} USDT`\\n} else {\\n gapInfo = '🏆 Congratulations! You are leading! Keep the advantage!'\\n}\\n\\nconst result = {\\n symbol: symbol,\\n timestamp: Date.now(),\\n timeframes: {\\n day: getIndicatorsForPeriod(symbol, timeframes.day, 'Daily'),\\n hour: getIndicatorsForPeriod(symbol, timeframes.hour, 'Hourly'),\\n min5: getIndicatorsForPeriod(symbol, timeframes.min5, '5-minute')\\n },\\n // ⚠️ Modified: Use model's own virtual position instead of real position\\n position: models[currentModelName].positions || [],\\n ranking: {\\n currentModel: currentModelName,\\n currentRank: currentModelRank.rank,\\n currentPnl: currentModelRank.realizedPnl,\\n currentTotalValue: currentModelRank.totalValue,\\n totalTrades: models[currentModelName].account.totalTrades,\\n winTrades: models[currentModelName].account.winTrades,\\n winRate: models[currentModelName].account.totalTrades > 0 ? \\n (models[currentModelName].account.winTrades / models[currentModelName].account.totalTrades * 100).toFixed(2) : '0',\\n rankingText: rankingText,\\n gapInfo: gapInfo,\\n totalModels: Object.keys(models).length,\\n allRankings: rankedModels\\n }\\n}\\n\\nreturn result\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-384,1216],\"id\":\"cd92d6e1-97bf-4e48-9c0f-73d2f7ea0c03\",\"name\":\"Grok Processing\"}],\"pinData\":{},\"connections\":{\"Schedule Trigger\":{\"main\":[[{\"node\":\"Strategy Init\",\"type\":\"main\",\"index\":0}]]},\"Claude4.5\":{\"ai_languageModel\":[[{\"node\":\"Claude\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"DS3.1\":{\"ai_languageModel\":[[{\"node\":\"DeepSeek\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"DeepSeek\":{\"main\":[[{\"node\":\"Merge\",\"type\":\"main\",\"index\":0}]]},\"Claude\":{\"main\":[[{\"node\":\"Merge\",\"type\":\"main\",\"index\":1}]]},\"QWEN\":{\"main\":[[{\"node\":\"Merge\",\"type\":\"main\",\"index\":2}]]},\"OpenAI Model\":{\"ai_languageModel\":[[{\"node\":\"QWEN\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"Grok\":{\"main\":[[{\"node\":\"Merge\",\"type\":\"main\",\"index\":3}]]},\"Grok4\":{\"ai_languageModel\":[[{\"node\":\"Grok\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"Merge\":{\"main\":[[{\"node\":\"Result Calculation\",\"type\":\"main\",\"index\":0}]]},\"Convert to File\":{\"main\":[[{\"node\":\"Write to File\",\"type\":\"main\",\"index\":0}]]},\"Result Calculation\":{\"main\":[[{\"node\":\"Trade Log Save\",\"type\":\"main\",\"index\":0}]]},\"Trade Log Save\":{\"main\":[[{\"node\":\"Convert to File\",\"type\":\"main\",\"index\":0},{\"node\":\"Best Trade\",\"type\":\"main\",\"index\":0}]]},\"Best Trade\":{\"main\":[[{\"node\":\"Visualization\",\"type\":\"main\",\"index\":0}]]},\"Strategy Init\":{\"main\":[[{\"node\":\"DS Processing\",\"type\":\"main\",\"index\":0},{\"node\":\"Claude Processing\",\"type\":\"main\",\"index\":0},{\"node\":\"QWEN Processing\",\"type\":\"main\",\"index\":0},{\"node\":\"Grok Processing\",\"type\":\"main\",\"index\":0}]]},\"DS Processing\":{\"main\":[[{\"node\":\"DeepSeek\",\"type\":\"main\",\"index\":0}]]},\"Claude Processing\":{\"main\":[[{\"node\":\"Claude\",\"type\":\"main\",\"index\":0}]]},\"QWEN Processing\":{\"main\":[[{\"node\":\"QWEN\",\"type\":\"main\",\"index\":0}]]},\"Grok Processing\":{\"main\":[[{\"node\":\"Grok\",\"type\":\"main\",\"index\":0}]]}},\"active\":false,\"settings\":{\"timezone\":\"Asia/Shanghai\",\"executionOrder\":\"v1\"},\"tags\":[],\"meta\":{\"templateCredsSetupCompleted\":true},\"credentials\":{},\"id\":\"6c8dfda8-4657-42fa-b4dc-8f723e88aaa7\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"Schedule Trigger\"}}"}