策略源码
{"type":"n8n","content":"{\"workflowData\":{\"nodes\":[{\"parameters\":{\"notice\":\"\",\"rule\":{\"interval\":[{\"field\":\"minutes\",\"minutesInterval\":3}]}},\"type\":\"n8n-nodes-base.scheduleTrigger\",\"typeVersion\":1.2,\"position\":[-960,48],\"id\":\"87b0809d-a969-4c5f-bef3-76c1299f3672\",\"name\":\"定时触发器\"},{\"parameters\":{\"mode\":\"append\",\"numberInputs\":2},\"type\":\"n8n-nodes-base.merge\",\"typeVersion\":3.2,\"position\":[-320,96],\"id\":\"66a9c86e-e8a9-4013-8e92-b80ffe70ca95\",\"name\":\"合并\"},{\"parameters\":{\"text\":\"=距离你开始交易已经过去了 {{ $node[\\\"参数重置\\\"].json.duringtime }} 分钟。当前时间是 {{ $now.toISO() }},你已经被调用了 {{ $node[\\\"参数重置\\\"].json.invoketime }} 次。下面我们为你提供了各种状态数据、价格数据和预测信号,以便你发现alpha机会。在这些数据下方是你当前的账户信息、价值、绩效、持仓等信息。\\n\\n**下面所有的价格或信号数据都按时间排序:从旧到新**\\n\\n时间框架说明:除非在章节标题中另有说明,否则日内序列数据以3分钟为间隔提供。如果某个合约使用不同的时间间隔,会在该合约的章节中明确说明。\\n\\n**所有合约的当前市场状态**\\n{{JSON.stringify($json.marketData)}}\\n\\n**以下是你的账户信息和绩效表现** \\n当前总收益率(百分比):{{ $node[\\\"参数重置\\\"].json.totalReturnPercent }} \\n可用现金:{{ $node[\\\"参数重置\\\"].json.availableCash }}\\n当前账户价值:{{ $node[\\\"参数重置\\\"].json.currentAccountValue }}\\n当前实时持仓及绩效: \\n{{JSON.stringify($json.positions)}}\",\"options\":{\"systemMessage\":\"=您是一位专业的中国商品期货波段交易员,管理着一个实盘期货投资组合。您每3分钟基于多时间框架技术分析和严格的风险管理原则做出交易决策。\\n\\n## 核心原则\\n**这是真实资金交易 - 宁可错过机会也不要接受低质量交易**\\n\\n## 波段交易专家特征\\n\\n### 1. **双向交易思维**\\n- 专业波段交易员从两个方向(上涨和下跌)都能获利\\n- 对做多(买入)和做空(卖出)同样熟练和舒适\\n\\n### 2. **市场阶段识别**\\n- **震荡/横盘市场**: 避免大多数交易,等待明确方向\\n- **上涨趋势市场**: 偏好做多,但也寻找超买时的做空机会\\n- **下跌趋势市场**: 偏好做空,但也寻找超跌反弹的做多机会\\n- **关键**: 在当前市场条件下找到最佳设置,不要强行施加方向偏好\\n\\n## 硬性约束\\n\\n### 持仓管理\\n- 可交易合约: {{$vars.contractList}}\\n- 最多{{$vars.contractList.split(',').length}}个并发持仓\\n- 不加仓或增加现有持仓\\n- 重新进入合约前必须先平仓\\n\\n### 风险管理\\n- 每笔交易最大风险: 账户价值的3-5%(根据信号质量调整)\\n- 最小风险回报比: 2.5:1\\n- 每个持仓必须有:\\n - 止损(具体价格)\\n - 盈利目标(具体价格)\\n - 失效条件(格式: \\\"如果价格在[时间框架]上收于[价格]下方/上方\\\")\\n\\n## 多时间框架分析框架\\n\\n### 可用时间框架\\n- **3分钟(快速)**: 入场时机、止损设置、短期动量\\n- **4小时(慢速)**: 整体趋势方向、市场结构、主要支撑/阻力\\n\\n## 技术分析原则\\n\\n### 通用原则\\n1. **趋势比位置重要**: 指标的变化方向比绝对值更重要\\n2. **情境比规则重要**: 根据市场状态灵活解读指标\\n3. **多重确认**: 多个指标同时确认比单一指标更可靠\\n4. **背离最重要**: 价格与指标的背离是最强的反转信号\\n\\n### 技术指标使用建议\\n\\n**您可以使用但不限于以下指标:**\\n- RSI: 观察趋势和背离,而非固定阈值\\n- MACD: 关注动量变化和交叉\\n- EMA: 作为动态支撑/阻力\\n- 成交量: 确认价格移动的有效性\\n- 价格形态: K线组合和图表形态\\n\\n**关键**: 根据您的经验和判断灵活使用这些工具,不必拘泥于特定规则。\\n\\n## 系统输出格式\\n\\n输出有效的JSON格式,每个合约一个键。\\n\\n### 信号类型\\n- **\\\"entry\\\"**: 开新仓(当前无持仓)\\n- **\\\"hold\\\"**: 继续持有现有持仓\\n- **\\\"close\\\"**: 平仓\\n\\n### 输出结构\\n```json\\n{\\n \\\"合约代码\\\": {\\n \\\"trade_signal_args\\\": {\\n \\\"contract\\\": \\\"合约代码\\\",\\n \\\"signal\\\": \\\"entry|hold|close\\\",\\n \\\"profit_target\\\": 数字或null,\\n \\\"stop_loss\\\": 数字或null,\\n \\\"invalidation_condition\\\": \\\"具体条件\\\"或null,\\n \\\"confidence\\\": 浮点数或null,\\n \\\"risk_usd\\\": 浮点数或null,\\n \\\"justification\\\": \\\"详细理由\\\"\\n }\\n }\\n}\\n```\\n\\n### 字段说明\\n\\n#### 所有信号必需字段:\\n- **contract**: 合约代码(如\\\"rb2505\\\")\\n- **signal**: \\\"entry\\\"、\\\"hold\\\"或\\\"close\\\"\\n- **justification**: 详细交易理由,必须包含4小时和3分钟的分析\\n\\n#### \\\"entry\\\"信号必需字段:\\n- **profit_target**: 目标价格\\n- **stop_loss**: 止损价格\\n- **invalidation_condition**: 失效条件(例如: \\\"如果价格在3分钟上收于3500下方\\\")\\n- **confidence**: 信心度(0.80-1.00)\\n- **risk_usd**: 本次交易的风险金额\\n\\n#### \\\"hold\\\"信号必需字段:\\n- **profit_target**: 当前目标价格\\n- **stop_loss**: 当前止损价格\\n- **invalidation_condition**: 继续持有的条件\\n- **confidence**: 当前信心度\\n- **risk_usd**: 当前风险金额\\n\\n#### \\\"close\\\"信号字段:\\n- **profit_target**: null\\n- **stop_loss**: null\\n- **invalidation_condition**: 平仓原因\\n- **confidence**: null\\n- **risk_usd**: null\\n\\n### 理由格式要求\\n\\n每个理由必须:\\n1. 先说明4小时时间框架的分析(趋势背景)\\n2. 再说明3分钟时间框架的分析(入场时机)\\n3. 提及关键观察指标和价格行为\\n4. 说明风险回报比(如果是入场)\\n5. 解释特殊因素(如有)\\n\\n**好的理由示例:**\\n```\\n\\\"做多: 4H趋势转强,MACD转正,价格突破关键阻力。3分钟: 价格站上均线,动量指标向上,形成更高低点。支撑位3500,目标3800,风险回报比3.0:1\\\"\\n```\\n\\n**不好的理由示例:**\\n```\\n\\\"看起来不错\\\" ← 太模糊\\n\\\"技术指标改善\\\" ← 缺乏具体细节\\n\\\"MACD金叉\\\" ← 只提单一信号,没有时间框架\\n```\\n\\n## 完整输出示例\\n\\n**示例1: 牛市环境(偏好做多):**\\n```json\\n{\\n \\\"rb2505\\\": {\\n \\\"trade_signal_args\\\": {\\n \\\"contract\\\": \\\"rb2505\\\",\\n \\\"signal\\\": \\\"entry\\\",\\n \\\"profit_target\\\": 3800.0,\\n \\\"stop_loss\\\": 3500.0,\\n \\\"invalidation_condition\\\": \\\"如果价格在3分钟上收于3520下方\\\",\\n \\\"confidence\\\": 0.85,\\n \\\"risk_usd\\\": 550.0,\\n \\\"justification\\\": \\\"做多: 4H强势上涨,价格突破3600阻力且站稳,MACD持续为正。3分钟: 回踩均线获得支撑,动量指标向上,成交量放大。风险回报比3.5:1\\\"\\n }\\n },\\n \\\"hc2505\\\": {\\n \\\"trade_signal_args\\\": {\\n \\\"contract\\\": \\\"hc2505\\\",\\n \\\"signal\\\": \\\"hold\\\",\\n \\\"profit_target\\\": 3900.0,\\n \\\"stop_loss\\\": 3650.0,\\n \\\"invalidation_condition\\\": \\\"如果价格在3分钟上收于3680下方\\\",\\n \\\"confidence\\\": 0.82,\\n \\\"risk_usd\\\": 480.0,\\n \\\"justification\\\": \\\"现有多头持仓保持良好。4H: 上升趋势延续,价格持续创新高。3分钟: 每次回调都有买盘承接,持仓继续持有至目标。\\\"\\n }\\n }\\n}\\n```\\n\\n**示例2: 混合市场(多空并存):**\\n```json\\n{\\n \\\"rb2505\\\": {\\n \\\"trade_signal_args\\\": {\\n \\\"contract\\\": \\\"rb2505\\\",\\n \\\"signal\\\": \\\"entry\\\",\\n \\\"profit_target\\\": 3750.0,\\n \\\"stop_loss\\\": 3520.0,\\n \\\"invalidation_condition\\\": \\\"如果价格在3分钟上收于3540下方\\\",\\n \\\"confidence\\\": 0.82,\\n \\\"risk_usd\\\": 450.0,\\n \\\"justification\\\": \\\"做多: 4H从超卖反弹,MACD底背离后转强。3分钟: 价格突破下降趋势线,动量转正,风险回报比2.9:1\\\"\\n }\\n },\\n \\\"hc2505\\\": {\\n \\\"trade_signal_args\\\": {\\n \\\"contract\\\": \\\"hc2505\\\",\\n \\\"signal\\\": \\\"entry\\\",\\n \\\"profit_target\\\": 3600.0,\\n \\\"stop_loss\\\": 3820.0,\\n \\\"invalidation_condition\\\": \\\"如果价格在3分钟上收于3800上方\\\",\\n \\\"confidence\\\": 0.83,\\n \\\"risk_usd\\\": 450.0,\\n \\\"justification\\\": \\\"做空: 4H高位滞涨,顶背离形成。3分钟: 价格跌破支撑,成交量放大,空头动能增强,风险回报比2.8:1\\\"\\n }\\n },\\n \\\"c2505\\\": {\\n \\\"trade_signal_args\\\": {\\n \\\"contract\\\": \\\"c2505\\\",\\n \\\"signal\\\": \\\"hold\\\",\\n \\\"profit_target\\\": 2680.0,\\n \\\"stop_loss\\\": 2580.0,\\n \\\"invalidation_condition\\\": \\\"如果价格在3分钟上收于2590下方\\\",\\n \\\"confidence\\\": 0.78,\\n \\\"risk_usd\\\": 400.0,\\n \\\"justification\\\": \\\"现有多头持仓继续有效。4H: 趋势向上但动能减弱,关注是否形成顶部。3分钟: 价格维持在关键支撑上方,暂时持有观察。\\\"\\n }\\n }\\n}\\n```\\n\\n## 决策流程\\n\\n**系统化的分析步骤:**\\n\\n### 第1步: 整体市场评估\\n1. 浏览所有合约的4小时走势\\n2. 统计: 多少看涨、多少看跌、多少中性\\n3. 确定整体市场偏向(牛市/熊市/混合)\\n4. 决定今天的主要交易方向\\n\\n### 第2步: 现有持仓管理\\n对每个有持仓的合约:\\n1. 检查失效条件是否触发\\n2. 评估4小时趋势是否仍然支持\\n3. 检查3分钟动量是否健康\\n4. 决定: 继续持有 或 平仓离场\\n\\n### 第3步: 寻找新机会\\n对每个无持仓的合约:\\n1. 确认是否还有开仓容量(资金和持仓数量)\\n2. 判断该合约的4小时趋势方向\\n3. 如果趋势明确,评估对应方向的入场机会\\n4. 检查3分钟是否给出入场时机\\n5. 计算风险回报比,评估信心度\\n6. 只接受信心度≥0.80的设置\\n\\n### 第4步: 质量控制\\n1. 检查所有决策是否合理一致\\n2. 确认风险分配是否适当\\n3. 验证理由是否清晰完整\\n4. 输出最终JSON\\n\\n## 关键规则\\n\\n1. **持仓状态**: 开新仓前必须确认该合约当前无持仓(quantity = null)\\n2. **时间框架对齐**: 4小时和3分钟必须同时确认才能入场\\n3. **方向一致性**: 保持投资组合方向与整体市场趋势基本一致\\n4. **信号纪律**:\\n - 有持仓的合约: 只能\\\"hold\\\"或\\\"close\\\"\\n - 无持仓的合约: 只能\\\"entry\\\"或不操作\\n5. **保守原则**: 有疑问时,不交易\\n6. **风险控制**: 单笔交易风险不超过账户的5%\\n\\n## 禁止行为\\n\\n- ❌ 输出非JSON格式内容\\n- ❌ 在时间框架不一致时入场\\n- ❌ 对有持仓的合约使用\\\"entry\\\"信号\\n- ❌ 对无持仓的合约使用\\\"hold\\\"或\\\"close\\\"信号\\n- ❌ 接受信心度低于0.80的交易\\n- ❌ 使用模糊或不完整的理由\\n- ❌ 忽视4小时的趋势方向\\n\\n## 交易哲学\\n\\n记住:\\n- **质量>数量**: 2-3个高质量交易好过10个平庸交易\\n- **保护资本**: 资本保护永远是第一位的\\n- **耐心等待**: 最好的交易往往是不交易\\n- **相信计划**: 除非明确失效,否则不要过早平仓\\n- **保持灵活**: 市场在变,我们也要适应\\n\\n---\\n\\n**现在请接收市场数据,运用多时间框架分析,输出您的交易决策(仅JSON格式)。**\"}},\"type\":\"@n8n/n8n-nodes-langchain.agent\",\"typeVersion\":1,\"position\":[96,96],\"id\":\"99af356d-26ce-40ab-8140-e7c9b86123c4\",\"name\":\"AI 智能体\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 初始化检查\\nif (_G('invoketime') === null) {\\n _G('invoketime', 0);\\n _G('STARTTIME', Date.now());\\n const initAccount = exchange.GetAccount();\\n _G('initmoney', initAccount.Equity);\\n}\\n\\n// 循环执行\\nconst invoketime = _G('invoketime') + 1;\\n_G('invoketime', invoketime);\\n\\nconst duringtime = Math.floor((Date.now() - _G('STARTTIME')) / 60000); // 转换为分钟\\nconst currentAccount = exchange.GetAccount();\\nconst currentAccountValue = currentAccount.Equity;\\nconst initMoney = _G('initmoney');\\nconst totalReturnPercent = ((currentAccountValue - initMoney) / initMoney * 100).toFixed(2);\\n\\nLogProfit(currentAccountValue - initMoney, \\\"&\\\")\\n\\n// 返回5个数据\\nreturn [{\\n json: {\\n invoketime: invoketime,\\n duringtime: duringtime, // 添加单位\\n totalReturnPercent: totalReturnPercent + '%',\\n availableCash: currentAccount.Balance.toFixed(2),\\n currentAccountValue: currentAccountValue.toFixed(2)\\n }\\n}];\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-816,48],\"id\":\"af9a7b1c-3a8f-4692-a8e9-edb26098e150\",\"name\":\"参数重置\"},{\"parameters\":{\"model\":{\"__rl\":true,\"value\":\"qwen3-max-preview\",\"mode\":\"list\",\"cachedResultName\":\"qwen3-max-preview\"}},\"type\":\"n8n-nodes-base.lmOpenAi\",\"typeVersion\":1,\"position\":[96,272],\"id\":\"29f09962-568a-4ffe-b4ba-9eea11ffe261\",\"name\":\"OpenAI 模型\",\"credentials\":{\"openAiApi\":{\"id\":\"f3da26db-84a0-4a0b-8e63-6bcc89a187e7\",\"name\":\"ali\"}}},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 解析品种列表\\nconst contracts = $vars.contractList ? ($vars.contractList.includes(',') ? $vars.contractList.split(',') : [$vars.contractList]) : [];\\n\\n\\nif (contracts.length === 0) {\\n return {};\\n}\\n\\nfunction getMarketDataForContract(symbol) {\\n exchange.SetContractType(symbol);\\n \\n const kline3m = exchange.GetRecords(PERIOD_M3);\\n const kline4h = exchange.GetRecords(PERIOD_H4);\\n \\n if (!kline3m || kline3m.length < 50 || !kline4h || kline4h.length < 50) {\\n return { error: \\\"K线数据不足\\\" };\\n }\\n \\n const ema20_3m = TA.EMA(kline3m, 20);\\n const macd_3m = TA.MACD(kline3m, 12, 26, 9);\\n const rsi7_3m = TA.RSI(kline3m, 7);\\n const rsi14_3m = TA.RSI(kline3m, 14);\\n \\n const ema20_4h = TA.EMA(kline4h, 20);\\n const ema50_4h = TA.EMA(kline4h, 50);\\n const macd_4h = TA.MACD(kline4h, 12, 26, 9);\\n const rsi14_4h = TA.RSI(kline4h, 14);\\n const atr3_4h = TA.ATR(kline4h, 3);\\n const atr14_4h = TA.ATR(kline4h, 14);\\n \\n const latest3m = kline3m[kline3m.length - 1];\\n const latest4h = kline4h[kline4h.length - 1];\\n const recent10_3m = kline3m.slice(-10);\\n const recent10_4h = kline4h.slice(-10);\\n \\n const volumes4h = recent10_4h.map(k => k.Volume);\\n const avgVolume4h = volumes4h.reduce((a, b) => a + b, 0) / volumes4h.length;\\n \\n return {\\n symbol: symbol,\\n current_price: latest3m.Close,\\n current_ema20: ema20_3m[ema20_3m.length - 1],\\n current_macd: macd_3m[2][macd_3m[2].length - 1],\\n current_rsi_7: rsi7_3m[rsi7_3m.length - 1],\\n intraday_3min: {\\n mid_prices: recent10_3m.map(k => k.Close),\\n ema_20_series: recent10_3m.map((k, i) => ema20_3m[ema20_3m.length - 10 + i]),\\n macd_series: recent10_3m.map((k, i) => macd_3m[2][macd_3m[2].length - 10 + i]),\\n rsi_7_series: recent10_3m.map((k, i) => rsi7_3m[rsi7_3m.length - 10 + i]),\\n rsi_14_series: recent10_3m.map((k, i) => rsi14_3m[rsi14_3m.length - 10 + i])\\n },\\n longer_term_4hour: {\\n ema_20: ema20_4h[ema20_4h.length - 1],\\n ema_50: ema50_4h[ema50_4h.length - 1],\\n atr_3: atr3_4h[atr3_4h.length - 1],\\n atr_14: atr14_4h[atr14_4h.length - 1],\\n current_volume: latest4h.Volume,\\n average_volume: avgVolume4h,\\n macd_series: recent10_4h.map((k, i) => macd_4h[2][macd_4h[2].length - 10 + i]),\\n rsi_14_series: recent10_4h.map((k, i) => rsi14_4h[rsi14_4h.length - 10 + i])\\n }\\n };\\n}\\n\\nconst allContractsData = {};\\nfor (let i = 0; i < contracts.length; i++) {\\n const contract = contracts[i]\\n try {\\n allContractsData[contract] = getMarketDataForContract(contract);\\n } catch (e) {\\n allContractsData[contract] = { error: e.toString() };\\n }\\n}\\n\\nreturn { data: allContractsData };\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-592,-48],\"id\":\"4a86b3ed-d938-4894-b9df-75a942756747\",\"name\":\"市场数据获取\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"function getTPSLOrderIds(symbol, currentPrice, posType) {\\n try {\\n // 获取该品种的未完成订单\\n exchange.SetContractType(symbol);\\n \\n const orders = exchange.GetOrders();\\n \\n if (!orders || orders.length === 0) {\\n return { tpOrderId: -1, slOrderId: -1 };\\n }\\n \\n let tpOrderId = -1;\\n let slOrderId = -1;\\n \\n for (let order of orders) {\\n // 多头持仓 (Type = 0 或 PD_LONG)\\n if (posType % 2 == 0) {\\n // 止盈:卖单价格 > 当前价\\n if (order.Type === ORDER_TYPE_SELL && order.Price > currentPrice) {\\n tpOrderId = order.Id;\\n }\\n // 止损:卖单价格 < 当前价\\n if (order.Type === ORDER_TYPE_SELL && order.Price < currentPrice) {\\n slOrderId = order.Id;\\n }\\n } \\n // 空头持仓\\n else {\\n // 止盈:买单价格 < 当前价\\n if (order.Type === ORDER_TYPE_BUY && order.Price < currentPrice) {\\n tpOrderId = order.Id;\\n }\\n // 止损:买单价格 > 当前价\\n if (order.Type === ORDER_TYPE_BUY && order.Price > currentPrice) {\\n slOrderId = order.Id;\\n }\\n }\\n }\\n \\n return { tpOrderId, slOrderId };\\n \\n } catch (e) {\\n Log(`⚠️ 获取 ${symbol} 订单失败: ${e.message}`);\\n return { tpOrderId: -1, slOrderId: -1 };\\n }\\n}\\n\\nfunction getAllPositions() {\\n // 获取当前账户权益\\n const curequity = exchange.GetAccount().Equity;\\n \\n // 获取品种列表\\n const symbols = $vars.contractList ? ($vars.contractList.includes(',') ? $vars.contractList.split(',') : [$vars.contractList]) : [];\\n \\n // 计算每个品种的risk_usd\\n const risk_usd = symbols.length > 0 ? curequity / symbols.length * 0.05 : 0;\\n \\n // 获取所有实际持仓\\n const rawPositions = exchange.GetPositions();\\n \\n // 创建持仓映射表 (品种符号 -> 持仓对象)\\n const positionMap = {};\\n \\n if (rawPositions && rawPositions.length > 0) {\\n for (let pos of rawPositions) {\\n if (pos.Amount && Math.abs(pos.Amount) > 0) {\\n positionMap[pos.Symbol] = pos;\\n }\\n }\\n }\\n \\n // 为每个品种创建position信息\\n const allPositions = [];\\n \\n for (let i = 0; i < symbols.length; i++) {\\n const contract = symbols[i].trim();\\n const pos = positionMap[contract];\\n \\n if (pos) {\\n // 有持仓的情况\\n try {\\n // 切换到对应交易对获取ticker\\n exchange.SetContractType(pos.Symbol);\\n \\n const ticker = exchange.GetTicker();\\n const currentPrice = ticker ? ticker.Last : pos.Price;\\n \\n // 获取止盈止损订单ID\\n const { tpOrderId, slOrderId } = getTPSLOrderIds(pos.Symbol, currentPrice, pos.Type);\\n \\n // 获取退出计划\\n const exitPlan = _G(`exit_plan_${pos.Symbol}`) || {\\n profit_target: null,\\n stop_loss: null,\\n invalidation_condition: \\\"\\\"\\n };\\n \\n allPositions.push({\\n symbol: contract,\\n quantity: Math.abs(pos.Amount),\\n entry_price: pos.Price,\\n current_price: currentPrice,\\n unrealized_pnl: _N(pos.Profit, 2),\\n exit_plan: exitPlan,\\n confidence: exitPlan?.confidence || null,\\n risk_usd: risk_usd, // 使用计算出的risk_usd\\n sl_oid: slOrderId, \\n tp_oid: tpOrderId, \\n wait_for_fill: false,\\n entry_oid: pos.Info?.posId || -1,\\n notional_usd: _N(Math.abs(pos.Amount) * currentPrice, 2)\\n });\\n } catch (e) {\\n Log(`⚠️ 处理 ${contract} 持仓信息失败: ${e.message}`);\\n // 出错时也添加一个空持仓记录\\n allPositions.push({\\n symbol: contract,\\n quantity: null,\\n entry_price: null,\\n current_price: null,\\n unrealized_pnl: null,\\n exit_plan: null,\\n confidence: null,\\n risk_usd: risk_usd,\\n sl_oid: null,\\n tp_oid: null,\\n wait_for_fill: false,\\n entry_oid: null,\\n notional_usd: null\\n });\\n }\\n } else {\\n // 没有持仓的情况 - 返回固定字段为null\\n allPositions.push({\\n symbol: contract,\\n quantity: null,\\n entry_price: null,\\n current_price: null,\\n unrealized_pnl: null,\\n exit_plan: null,\\n confidence: null,\\n risk_usd: risk_usd, // 仍然返回risk_usd\\n sl_oid: null,\\n tp_oid: null,\\n wait_for_fill: false,\\n entry_oid: null,\\n notional_usd: null\\n });\\n }\\n }\\n \\n return allPositions;\\n}\\n\\nconst positions = getAllPositions();\\nreturn {positions};\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-592,192],\"id\":\"7d76bbba-09f9-4fbf-b87c-6e526f40207d\",\"name\":\"持仓数据获取\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// 获取输入数据\\nconst inputData = $input.all();\\n\\n// 第一个输入是市场数据,第二个是持仓数据\\nconst marketData = inputData[0].json.data;\\nconst positions = inputData[1].json;\\n\\n// 返回整理后的数据\\nreturn [{\\n json: {\\n marketData: marketData,\\n positions: positions\\n }\\n}];\\n\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[-112,96],\"id\":\"17317886-4e44-4532-bf0b-4dd742dc9539\",\"name\":\"数据合并\"},{\"parameters\":{\"mode\":\"runOnceForAllItems\",\"language\":\"javaScript\",\"jsCode\":\"// ========== 工具函数 ==========\\n\\nfunction parseAIOutput(output) {\\n try {\\n const cleaned = output.replace(/```[a-z]*\\\\n?/gi, '').trim();\\n const start = cleaned.indexOf('{');\\n const end = cleaned.lastIndexOf('}');\\n return JSON.parse(cleaned.substring(start, end + 1));\\n } catch (e) {\\n return {};\\n }\\n}\\n\\n// ========== 数量计算函数 ==========\\nfunction calculateQuantity(entryPrice, stopLoss, riskUsd) {\\n const riskPerUnit = Math.abs(entryPrice - stopLoss) * 10;\\n if (riskPerUnit <= 0) return 0;\\n const quantity = riskUsd / riskPerUnit;\\n return quantity;\\n}\\n\\nfunction validateEntry(contract, currentPrice, profitTarget, stopLoss) {\\n const isLong = profitTarget > stopLoss;\\n if (isLong && (profitTarget <= currentPrice || stopLoss >= currentPrice)) return false;\\n if (!isLong && (profitTarget >= currentPrice || stopLoss <= currentPrice)) return false;\\n return true;\\n}\\n\\nfunction hasPosition(contract) {\\n try {\\n exchange.SetContractType(contract);\\n const positions = exchange.GetPositions();\\n return positions.some(p => p.Symbol.includes(contract) && Math.abs(p.Amount) > 0);\\n } catch (e) {\\n return false;\\n }\\n}\\n\\nfunction saveExitPlan(symbol, args) {\\n _G(`exit_plan_${symbol}`, {\\n profit_target: args.profit_target,\\n stop_loss: args.stop_loss,\\n invalidation_condition: args.invalidation_condition || \\\"\\\",\\n confidence: args.confidence,\\n risk_usd: args.risk_usd\\n });\\n}\\n\\n// ========== 上期所平仓规则判断 ==========\\n\\nfunction isSHFE(exchangeId) {\\n return exchangeId === \\\"SHFE\\\";\\n}\\n\\nfunction closePositionSHFE(contract, pos, isLong, closeAmount) {\\n const info = pos.Info;\\n const exchangeId = info?.ExchangeID || \\\"\\\";\\n \\n if (!isSHFE(exchangeId)) {\\n // 非上期所,普通平仓\\n exchange.SetDirection(isLong ? \\\"closebuy\\\" : \\\"closesell\\\");\\n const orderId = isLong ? exchange.Sell(-1, closeAmount) : exchange.Buy(-1, closeAmount);\\n return orderId;\\n }\\n \\n // 上期所需要区分平今平昨\\n const todayPosition = info.TodayPosition || 0;\\n const ydPosition = info.YdPosition || 0;\\n \\n let remainingClose = closeAmount;\\n const orderIds = [];\\n \\n // 优先平昨仓\\n if (ydPosition > 0 && remainingClose > 0) {\\n const closeYd = Math.min(ydPosition, remainingClose);\\n exchange.SetDirection(isLong ? \\\"closebuy\\\" : \\\"closesell\\\");\\n const orderId = isLong ? exchange.Sell(-1, closeYd) : exchange.Buy(-1, closeYd);\\n if (orderId) {\\n orderIds.push(orderId);\\n remainingClose -= closeYd;\\n }\\n Sleep(300);\\n }\\n \\n // 再平今仓\\n if (todayPosition > 0 && remainingClose > 0) {\\n const closeToday = Math.min(todayPosition, remainingClose);\\n exchange.SetDirection(isLong ? \\\"closebuy_today\\\" : \\\"closesell_today\\\");\\n const orderId = isLong ? exchange.Sell(-1, closeToday) : exchange.Buy(-1, closeToday);\\n if (orderId) {\\n orderIds.push(orderId);\\n remainingClose -= closeToday;\\n }\\n }\\n \\n return orderIds.length > 0 ? orderIds[0] : null;\\n}\\n\\n// ========== 交易执行函数 ==========\\n\\nfunction executeClose(contract) {\\n exchange.SetContractType(contract);\\n \\n const orders = exchange.GetOrders();\\n orders?.forEach(o => exchange.CancelOrder(o.Id));\\n \\n const pos = exchange.GetPositions().find(p => p.Symbol.includes(contract) && Math.abs(p.Amount) > 0);\\n if (!pos) return;\\n \\n const isLong = pos.Amount > 0;\\n const closeAmount = Math.abs(pos.Amount);\\n \\n const orderId = closePositionSHFE(contract, pos, isLong, closeAmount);\\n \\n if (orderId) {\\n Log(`✅ ${contract}: 平${isLong ? '多' : '空'}成功,数量=${closeAmount}`);\\n _G(`exit_plan_${contract}`, null);\\n }\\n}\\n\\nfunction executeEntry(contract, args) {\\n exchange.SetContractType(contract);\\n \\n const ticker = exchange.GetTicker();\\n if (!ticker){\\n Log('获取不到ticker')\\n return\\n };\\n \\n const currentPrice = ticker.Last;\\n if (!validateEntry(contract, currentPrice, args.profit_target, args.stop_loss)) {\\n Log('定义开仓失效')\\n return\\n }\\n \\n const quantity = calculateQuantity(currentPrice, args.stop_loss, args.risk_usd);\\n if (quantity <= 0) {\\n Log(`⚠️ ${contract}: 计算数量无效,跳过开仓`);\\n return;\\n }\\n \\n const isLong = args.profit_target > args.stop_loss;\\n exchange.SetDirection(isLong ? \\\"buy\\\" : \\\"sell\\\");\\n \\n const orderId = isLong ? exchange.Buy(-1, quantity) : exchange.Sell(-1, quantity);\\n \\n if (orderId) {\\n Sleep(1000);\\n Log(`✅ ${contract}: 开${isLong ? '多' : '空'} 成功 数量=${quantity}`);\\n } else {\\n Log(`❌ ${contract}: 开仓失败`);\\n }\\n}\\n\\n// ========== 止盈止损监控 ==========\\n\\nfunction monitorPosition(contract) {\\n exchange.SetContractType(contract);\\n \\n const pos = exchange.GetPositions().find(p => p.Symbol.includes(contract) && Math.abs(p.Amount) > 0);\\n if (!pos) return;\\n \\n const ticker = exchange.GetTicker();\\n if (!ticker) return;\\n \\n const isLong = pos.Amount > 0;\\n const currentPrice = ticker.Last;\\n const pnl = (currentPrice - pos.Price) * (isLong ? 1 : -1) / pos.Price;\\n \\n const exitPlan = _G(`exit_plan_${contract}`);\\n if (!exitPlan?.profit_target || !exitPlan?.stop_loss) {\\n if (pnl >= 0.03) return closePosition(contract, pos, isLong, \\\"止盈\\\", pnl);\\n if (pnl <= -0.01) return closePosition(contract, pos, isLong, \\\"止损\\\", pnl);\\n return;\\n }\\n \\n const shouldTP = isLong ? currentPrice >= exitPlan.profit_target : currentPrice <= exitPlan.profit_target;\\n const shouldSL = isLong ? currentPrice <= exitPlan.stop_loss : currentPrice >= exitPlan.stop_loss;\\n \\n if (shouldTP) return closePosition(contract, pos, isLong, \\\"止盈\\\", pnl);\\n if (shouldSL) return closePosition(contract, pos, isLong, \\\"止损\\\", pnl);\\n}\\n\\nfunction closePosition(contract, pos, isLong, reason, pnl) {\\n const closeAmount = Math.abs(pos.Amount);\\n \\n closePositionSHFE(contract, pos, isLong, closeAmount);\\n \\n Log(`${reason === '止盈' ? '✅' : '❌'} ${contract} ${reason} ${(pnl * 100).toFixed(2)}%`);\\n _G(`exit_plan_${contract}`, null);\\n}\\n\\n// ========== 主逻辑 ==========\\n\\nconst signals = parseAIOutput($input.first().json.output);\\nLog('信号:', signals);\\n\\n// AI信号表格\\nconst signalTable = {\\n type: \\\"table\\\",\\n title: \\\"📊 AI交易信号分析\\\",\\n cols: [\\\"品种\\\", \\\"信号\\\", \\\"止盈目标\\\", \\\"止损价位\\\", \\\"风险金额\\\", \\\"置信度\\\", \\\"信号条件\\\"],\\n rows: []\\n};\\n\\nfor (const [contract, data] of Object.entries(signals)) {\\n const args = data?.trade_signal_args;\\n if (!args?.contract || !args?.signal) continue;\\n \\n const hasPos = hasPosition(contract);\\n let displaySignal = args.signal;\\n let signalEmoji = \\\"⏸️\\\";\\n \\n if ((args.signal === \\\"hold\\\" && !hasPos) || (args.signal === \\\"close\\\" && !hasPos)) {\\n displaySignal = \\\"无操作\\\";\\n signalEmoji = \\\"⚪\\\";\\n } else if (args.signal === \\\"entry\\\") {\\n const isLong = args.profit_target > args.stop_loss;\\n displaySignal = isLong ? \\\"做多入场\\\" : \\\"做空入场\\\";\\n signalEmoji = isLong ? \\\"🟢\\\" : \\\"🔴\\\";\\n } else if (args.signal === \\\"close\\\") {\\n displaySignal = \\\"平仓\\\";\\n signalEmoji = \\\"⏹️\\\";\\n } else if (args.signal === \\\"hold\\\") {\\n displaySignal = \\\"持仓观望\\\";\\n signalEmoji = \\\"⏸️\\\";\\n }\\n \\n const confidence = args.confidence ? (args.confidence * 100).toFixed(0) : 0;\\n let confidenceDisplay = \\\"-\\\";\\n if (confidence >= 70) confidenceDisplay = `🔥 ${confidence}%`;\\n else if (confidence >= 50) confidenceDisplay = `⚡ ${confidence}%`;\\n else if (confidence > 0) confidenceDisplay = `⚠️ ${confidence}%`;\\n \\n signalTable.rows.push([\\n `💎 ${contract}`,\\n `${signalEmoji} ${displaySignal}`,\\n args.profit_target ? `$${args.profit_target}` : \\\"-\\\",\\n args.stop_loss ? `$${args.stop_loss}` : \\\"-\\\",\\n args.risk_usd ? `💰 $${args.risk_usd}` : \\\"-\\\",\\n confidenceDisplay,\\n args.justification\\n ]);\\n \\n if (args.signal === \\\"hold\\\" && !hasPos) continue;\\n if (args.signal === \\\"close\\\" && !hasPos) continue;\\n if (args.signal === \\\"entry\\\" && hasPos) continue;\\n \\n if (args.signal !== \\\"close\\\") saveExitPlan(contract, args);\\n \\n if (args.signal === \\\"close\\\") executeClose(contract);\\n else if (args.signal === \\\"hold\\\") Log(`⏸️ ${contract}: 持仓`);\\n else if (args.signal === \\\"entry\\\") {\\n if (!args.profit_target || !args.stop_loss || !args.risk_usd) continue;\\n executeEntry(contract, args);\\n }\\n}\\n\\n// 监控止盈止损并生成持仓表\\nconst positionTable = {\\n type: \\\"table\\\",\\n title: \\\"💼 当前持仓监控\\\",\\n cols: [\\\"品种\\\", \\\"方向\\\", \\\"持仓量\\\", \\\"入场价\\\", \\\"当前价\\\", \\\"盈亏\\\", \\\"止盈目标\\\", \\\"止损价位\\\"],\\n rows: []\\n};\\n\\nconst positions = exchange.GetPositions();\\nlet totalPnl = 0, positionCount = 0;\\n\\npositions?.forEach(pos => {\\n if (Math.abs(pos.Amount) > 0) {\\n const contract = pos.Symbol;\\n exchange.SetContractType(contract);\\n const ticker = exchange.GetTicker();\\n \\n if (ticker) {\\n const isLong = pos.Amount > 0;\\n const currentPrice = ticker.Last;\\n const pnlPercent = ((currentPrice - pos.Price) * (isLong ? 1 : -1) / pos.Price * 100);\\n \\n let pnlDisplay = pnlPercent > 0 ? `🟢 +${pnlPercent.toFixed(2)}%` :\\n pnlPercent < 0 ? `🔴 ${pnlPercent.toFixed(2)}%` :\\n `⚪ ${pnlPercent.toFixed(2)}%`;\\n \\n const exitPlan = _G(`exit_plan_${contract}`);\\n positionTable.rows.push([\\n `💎 ${contract}`,\\n isLong ? \\\"📈 多\\\" : \\\"📉 空\\\",\\n pos.Amount.toFixed(2),\\n `$${pos.Price}`,\\n `$${currentPrice}`,\\n pnlDisplay,\\n exitPlan?.profit_target ? `🎯 $${exitPlan.profit_target}` : \\\"-\\\",\\n exitPlan?.stop_loss ? `🛡️ $${exitPlan.stop_loss}` : \\\"-\\\"\\n ]);\\n \\n totalPnl += pnlPercent;\\n positionCount++;\\n }\\n monitorPosition(contract);\\n Sleep(500);\\n }\\n});\\n\\nif (positionCount > 0) {\\n const avgPnl = totalPnl / positionCount;\\n const summaryEmoji = avgPnl > 0 ? \\\"📊 ✅\\\" : \\\"📊 ⚠️\\\";\\n positionTable.rows.push([\\n `${summaryEmoji} 汇总`,\\n `${positionCount} 个持仓`,\\n \\\"-\\\",\\\"-\\\",\\\"-\\\",\\n `平均: ${avgPnl > 0 ? '🟢' : '🔴'} ${avgPnl.toFixed(2)}%`,\\n \\\"-\\\",\\\"-\\\"\\n ]);\\n}\\n\\nLogStatus('`' + JSON.stringify(signalTable) + '`\\\\n\\\\n' + '`' + JSON.stringify(positionTable) + '`');\\n\\nreturn { json: { processed: true } };\",\"notice\":\"\"},\"type\":\"n8n-nodes-base.code\",\"typeVersion\":2,\"position\":[448,96],\"id\":\"9b8e0f35-6686-4484-a629-01e03c485bd7\",\"name\":\"交易执行\"}],\"pinData\":{},\"connections\":{\"定时触发器\":{\"main\":[[{\"node\":\"参数重置\",\"type\":\"main\",\"index\":0}]]},\"合并\":{\"main\":[[{\"node\":\"数据合并\",\"type\":\"main\",\"index\":0}]]},\"AI 智能体\":{\"main\":[[{\"node\":\"交易执行\",\"type\":\"main\",\"index\":0}]]},\"参数重置\":{\"main\":[[{\"node\":\"市场数据获取\",\"type\":\"main\",\"index\":0},{\"node\":\"持仓数据获取\",\"type\":\"main\",\"index\":0}]]},\"OpenAI 模型\":{\"ai_languageModel\":[[{\"node\":\"AI 智能体\",\"type\":\"ai_languageModel\",\"index\":0}]]},\"市场数据获取\":{\"main\":[[{\"node\":\"合并\",\"type\":\"main\",\"index\":0}]]},\"持仓数据获取\":{\"main\":[[{\"node\":\"合并\",\"type\":\"main\",\"index\":1}]]},\"数据合并\":{\"main\":[[{\"node\":\"AI 智能体\",\"type\":\"main\",\"index\":0}]]}},\"active\":false,\"settings\":{\"timezone\":\"Asia/Shanghai\",\"executionOrder\":\"v1\"},\"tags\":[],\"credentials\":{},\"id\":\"9d14748b-e818-41c3-835a-48aaf12a9a2d\",\"plugins\":{},\"mcpClients\":{}},\"startNodes\":[],\"triggerToStartFrom\":{\"name\":\"定时触发器\"}}"}