Type/to search
9
Follow
17
Followers
मुझे अनलिमिटेड ग्रिड कॉन्ट्रैक्ट्स (लॉन्ग और शॉर्ट पोजीशन) के ऑर्डर की सटीकता और निष्पादन में समस्या आ रही है। मैं पिछले आधे महीने से इस पर काम कर रहा हूँ और पूरी तरह से अटक गया हूँ। मैं समाधान के लिए...
Help
Created 2025-12-08 11:00:34  Updated 2025-12-08 11:27:50
 11
 733
javascript
/*backtest start: 2024-01-01 00:00:00 end: 2024-12-31 23:59:00 period: 1m basePeriod: 1m exchanges: [{"eid":"OKX","currency":"BTC_USDT","balance":10000}] */ // ==================== FMZ平台参数配置(从界面获取,禁止硬编码)==================== // 注意:FMZ平台中,界面设置的参数名称会自动成为JavaScript全局变量 // 直接使用变量名即可访问参数值,不需要使用GetConfig()函数 // 界面参数名必须与代码中的变量名一致 function getConfigParams() { var config = { // 币种选择(从FMZ界面选择,界面参数名:symbol) symbol: symbol || "BTC/USDT", // 网格参数(全部从界面输入,禁止硬编码) longGridSpacing: parseFloat(longGridSpacing) || 1.0, // 多头网格间距(%),界面参数名:longGridSpacing shortGridSpacing: parseFloat(shortGridSpacing) || 1.0, // 空头网格间距(%),界面参数名:shortGridSpacing orderAmount: parseFloat(orderAmount) || 100, // 单个网格下单金额,界面参数名:orderAmount upperPrice: parseFloat(upperPrice), // 网格上边界价格,界面参数名:upperPrice lowerPrice: parseFloat(lowerPrice), // 网格下边界价格,界面参数名:lowerPrice // 可选:手动覆盖最小步长/数量精度(界面参数名:forceMinLotSize, forceAmountPrecision) forceMinLotSize: forceMinLotSize ? parseFloat(forceMinLotSize) : null, forceAmountPrecision: forceAmountPrecision ? parseInt(forceAmountPrecision, 10) : null, }; // ❌ 禁止硬编码示例: // config.longGridSpacing = 1.0; // 错误! // config.orderAmount = 100; // 错误! return config; } // ==================== 状态持久化修正版 ==================== // 使用FMZ平台的_G函数实现状态持久化 // 关机或重启后可以自动恢复状态 function saveState() { /** 保存策略状态到全局存储 */ try { var state = { // 多头相关 MANY_NEXT_BUY_PRICE: MANY_NEXT_BUY_PRICE, MANY_ORDER_LIST: MANY_ORDER_LIST, // 空头相关 SHORT_NEXT_BUY_PRICE: SHORT_NEXT_BUY_PRICE, SHORT_ORDER_LIST: SHORT_ORDER_LIST, // 状态信息相关 INIT_TIME: INIT_TIME, INITIAL_BALANCE: INITIAL_BALANCE, // 配置信息(用于参数迁移检测) config: config, // 收益历史(保留最近1000条) PROFIT_HISTORY: PROFIT_HISTORY.slice(-1000), // 参数迁移相关 oldConfig: oldConfig, isMigrating: isMigrating, // 时间戳 lastSaveTime: Unix() }; // ✅ 修正:使用正确的FMZ全局存储函数 _G("strategy_state", JSON.stringify(state)); return true; } catch (e) { Log("保存状态失败:", e); return false; } } function loadState() { /** 从全局存储恢复策略状态 */ try { // ✅ 修正:使用正确的FMZ全局存储函数 var stateStr = _G("strategy_state"); if (!stateStr) { Log("未找到保存的状态,使用初始状态"); return false; } var state = JSON.parse(stateStr); // 恢复多头相关 if (state.MANY_NEXT_BUY_PRICE !== undefined) { MANY_NEXT_BUY_PRICE = state.MANY_NEXT_BUY_PRICE; } if (state.MANY_ORDER_LIST && Array.isArray(state.MANY_ORDER_LIST)) { MANY_ORDER_LIST = state.MANY_ORDER_LIST; } // 恢复空头相关 if (state.SHORT_NEXT_BUY_PRICE !== undefined) { SHORT_NEXT_BUY_PRICE = state.SHORT_NEXT_BUY_PRICE; } if (state.SHORT_ORDER_LIST && Array.isArray(state.SHORT_ORDER_LIST)) { SHORT_ORDER_LIST = state.SHORT_ORDER_LIST; } // 恢复状态信息 if (state.INIT_TIME) { INIT_TIME = state.INIT_TIME; } if (state.INITIAL_BALANCE) { INITIAL_BALANCE = state.INITIAL_BALANCE; } // 恢复收益历史 if (state.PROFIT_HISTORY && Array.isArray(state.PROFIT_HISTORY)) { PROFIT_HISTORY = state.PROFIT_HISTORY; } // 恢复参数迁移状态 if (state.oldConfig) { oldConfig = state.oldConfig; } if (state.isMigrating !== undefined) { isMigrating = state.isMigrating; } Log("状态恢复成功 - 多头持仓数:", MANY_ORDER_LIST.length, "空头持仓数:", SHORT_ORDER_LIST.length); if (state.lastSaveTime) { var downtime = Unix() - state.lastSaveTime; Log("上次保存时间:", new Date(state.lastSaveTime * 1000).toLocaleString(), "停机时长:", Math.floor(downtime / 60), "分钟"); } return true; } catch (e) { Log("恢复状态失败:", e); return false; } } function saveHistory(record) { /** 保存历史记录到全局存储 */ try { var historyKey = "strategy_history"; var historyStr = _G(historyKey); var history = []; if (historyStr) { try { history = JSON.parse(historyStr); } catch (e) { history = []; } } // 添加新记录 record.timestamp = Unix(); history.push(record); // 只保留最近5000条记录 if (history.length > 5000) { history = history.slice(-5000); } _G(historyKey, JSON.stringify(history)); return true; } catch (e) { Log("保存历史记录失败:", e); return false; } } function getHistory(limit) { /** 获取历史记录 */ try { var historyKey = "strategy_history"; var historyStr = _G(historyKey); if (!historyStr) { return []; } var history = JSON.parse(historyStr); limit = limit || 100; // 返回最近的记录 return history.slice(-limit); } catch (e) { Log("获取历史记录失败:", e); return []; } } // ==================== 安全的状态保存 ==================== function safeSaveState() { /** 安全保存状态,带错误处理 */ try { if (saveState()) { LAST_SAVE_TIME = Unix(); return true; } return false; } catch (e) { Log("安全保存状态失败:", e); return false; } } // ==================== 持仓对账 ==================== function reconcilePositions() { try { var positions = exchange.GetPosition(); if (!positions) { return; } var hasLong = false; var hasShort = false; for (var i = 0; i < positions.length; i++) { if (positions[i].Amount > 0) { if (positions[i].Type === 0) hasLong = true; if (positions[i].Type === 1) hasShort = true; } } // 防抖:连续多次空才清空本地 if (!hasLong && !hasShort) { RECONCILE_EMPTY_STREAK += 1; } else { RECONCILE_EMPTY_STREAK = 0; } var shouldClear = RECONCILE_EMPTY_STREAK >= RECONCILE_EMPTY_THRESHOLD; if (shouldClear) { if (!hasLong && MANY_ORDER_LIST.length > 0) { Log("对账:连续空结果,实际多头为空,清空本地多头持仓列表"); MANY_ORDER_LIST = []; MANY_BUYING = false; } if (!hasShort && SHORT_ORDER_LIST.length > 0) { Log("对账:连续空结果,实际空头为空,清空本地空头持仓列表"); SHORT_ORDER_LIST = []; SHORT_BUYING = false; } if ((!hasLong && MANY_ORDER_LIST.length === 0) || (!hasShort && SHORT_ORDER_LIST.length === 0)) { safeSaveState(); } } } catch (e) { Log("持仓对账失败:", e); } } // ==================== 双轨制参数迁移 ==================== // 实现渐进式参数迁移,保持原有持仓的平仓逻辑,新开仓使用新参数 // ==================== 增强的参数迁移检测 ==================== function checkConfigChange(newConfig) { /** 检查配置是否发生变化,如果变化则启动参数迁移 */ if (!oldConfig) { // 首次运行,保存当前配置 oldConfig = JSON.parse(JSON.stringify(newConfig)); isMigrating = false; return false; } // 详细检查参数变化 var configChanged = false; var changes = []; if (oldConfig.longGridSpacing !== newConfig.longGridSpacing) { configChanged = true; changes.push("多头间距: " + oldConfig.longGridSpacing + "% → " + newConfig.longGridSpacing + "%"); } if (oldConfig.shortGridSpacing !== newConfig.shortGridSpacing) { configChanged = true; changes.push("空头间距: " + oldConfig.shortGridSpacing + "% → " + newConfig.shortGridSpacing + "%"); } if (oldConfig.orderAmount !== newConfig.orderAmount) { configChanged = true; changes.push("下单金额: " + oldConfig.orderAmount + " → " + newConfig.orderAmount); } if (oldConfig.upperPrice !== newConfig.upperPrice) { configChanged = true; changes.push("上边界: " + oldConfig.upperPrice + " → " + newConfig.upperPrice); } if (oldConfig.lowerPrice !== newConfig.lowerPrice) { configChanged = true; changes.push("下边界: " + oldConfig.lowerPrice + " → " + newConfig.lowerPrice); } if (configChanged && !isMigrating) { Log("========== 🚀 检测到参数变化,启动双轨制迁移 =========="); changes.forEach(function(change) { Log("📝 " + change); }); Log("🔄 迁移模式:旧持仓使用旧参数平仓,新开仓使用新参数"); Log("📊 当前持仓 - 多头:", MANY_ORDER_LIST.length, "空头:", SHORT_ORDER_LIST.length); isMigrating = true; safeSaveState(); // 立即保存迁移状态 } return configChanged; } function shouldUseOldConfig(side) { /** 判断是否应该使用旧配置(用于平仓逻辑) */ if (!isMigrating || !oldConfig) { return false; } // 如果还有旧持仓,使用旧配置平仓 if (side === LONG_SIDE) { return MANY_ORDER_LIST.length > 0; } else if (side === SHORT_SIDE) { return SHORT_ORDER_LIST.length > 0; } return false; } function getGridSpacing(side, isClose) { /** 获取网格间距(支持双轨制) */ if (isClose && shouldUseOldConfig(side)) { // 平仓时,如果有旧持仓,使用旧参数 return side === LONG_SIDE ? oldConfig.longGridSpacing : oldConfig.shortGridSpacing; } else { // 开仓时,或旧持仓已平完,使用新参数 return side === LONG_SIDE ? config.longGridSpacing : config.shortGridSpacing; } } function getOrderAmount(isClose) { /** 获取下单金额(支持双轨制) */ if (isClose && (shouldUseOldConfig(LONG_SIDE) || shouldUseOldConfig(SHORT_SIDE))) { // 平仓时,如果有旧持仓,使用旧参数 return oldConfig.orderAmount; } else { // 开仓时,使用新参数 return config.orderAmount; } } function checkMigrationComplete() { /** 检查参数迁移是否完成 */ if (!isMigrating) { return true; } // 如果旧持仓已全部平完,迁移完成 var oldLongPositions = 0; var oldShortPositions = 0; // 这里需要根据实际持仓判断,简化处理:如果持仓列表为空,认为迁移完成 // 实际应该检查持仓是否是用旧参数开的 if (MANY_ORDER_LIST.length === 0 && SHORT_ORDER_LIST.length === 0) { Log("========== ✅ 参数迁移完成 =========="); oldConfig = null; isMigrating = false; safeSaveState(); // 保存迁移完成状态 return true; } return false; } // ==================== 精度管理 ==================== function getDecimalPlaces(num) { /** 获取数字的小数位数 */ var str = num.toString(); if (str.indexOf('.') > -1) { return str.split('.')[1].length; } return 0; } function getPrecision() { /** * 获取下单精度 * 1) 若已从规则获取 tickSz/lotSz,则直接使用 DEFAULT_PRICE_PREC / MIN_LOT_DECIMALS * 2) 否则从深度数据推断 * 3) 否则使用默认 */ if (DEFAULT_PRICE_PREC && MIN_LOT_DECIMALS) { return { pricePrecision: DEFAULT_PRICE_PREC, amountPrecision: forceAmountPrecision || MIN_LOT_DECIMALS }; } try { var depth = exchange.GetDepth(); if (depth && depth.Bids && depth.Bids.length > 0) { var bidPrice = depth.Bids[0].Price; var bidAmount = depth.Bids[0].Amount; var pricePrecision = getDecimalPlaces(bidPrice); var amountPrecision = getDecimalPlaces(bidAmount); Log("从深度数据获取精度 - 买一价格:", bidPrice, "价格精度:", pricePrecision, "买一数量:", bidAmount, "数量精度:", amountPrecision); return { pricePrecision: Math.max(1, pricePrecision || DEFAULT_PRICE_PREC || 1), amountPrecision: Math.max(MIN_LOT_DECIMALS, amountPrecision || MIN_LOT_DECIMALS) }; } } catch (e) { Log("从深度数据获取精度失败:", e); } Log("使用默认精度 - 价格精度:", DEFAULT_PRICE_PREC, "数量精度:", MIN_LOT_DECIMALS); return { pricePrecision: DEFAULT_PRICE_PREC, amountPrecision: forceAmountPrecision || MIN_LOT_DECIMALS }; } function formatPrice(price, precision) { return parseFloat(price.toFixed(precision)); } function formatAmount(amount, precision) { return parseFloat(amount.toFixed(precision)); } function calculateOrderAmount(price, orderValue, amountPrecision) { /** * 计算订单数量 * 确保数量是合约最小单位的整数倍 * ETH永续合约最小单位:0.01 */ var minLotSize = MIN_LOT_SIZE; // ETH永续合约最小单位 // 计算基础数量 var amount = orderValue / price; Log("计算订单数量 - 基础计算: 目标金额=" + orderValue + ", 价格=" + price + ", 基础数量=" + amount.toFixed(6)); // 确保数量是minLotSize的整数倍(向上取整) amount = Math.round(amount / minLotSize) * minLotSize; if (amount < minLotSize) { amount = minLotSize; } Log("计算订单数量 - 对齐到步长: " + amount.toFixed(amtPrecision)); // 应用精度格式化(数量精度改为2位) var amtPrecision = Math.max(amountPrecision || (forceAmountPrecision || MIN_LOT_DECIMALS), MIN_LOT_DECIMALS); amount = formatAmount(amount, amtPrecision); // 按传入精度格式化 // 再次确保是minLotSize的整数倍(防止精度格式化后丢失) amount = Math.ceil(amount / minLotSize) * minLotSize; amount = parseFloat(amount.toFixed(amtPrecision)); // 确保符合数量精度 // 计算实际订单金额 var actualValue = amount * price; // 容错处理:允许订单金额与目标金额有较大偏差(允许10-20%的偏差) // 如果实际金额太小(小于目标金额的80%),则增加一个最小单位 if (actualValue < orderValue * 0.8) { amount = amount + minLotSize; amount = parseFloat(amount.toFixed(amtPrecision)); actualValue = amount * price; Log("计算订单数量 - 金额太小,增加一个最小单位: " + amount.toFixed(amtPrecision)); } var deviation = ((actualValue - orderValue) / orderValue * 100).toFixed(2); Log("计算订单数量 - 最终结果: 目标金额=" + orderValue + ", 价格=" + price + ", 计算数量=" + amount.toFixed(2) + ", 实际金额=" + actualValue.toFixed(2) + ", 偏差=" + deviation + "%"); return amount; } // ==================== 工具函数 ==================== function safeAmount(raw) { var val = Number(raw); return (isNaN(val) || val <= 0) ? 0.001 : Math.max(val, 0.001); } function safePrice(raw) { var val = Number(raw); return (isNaN(val) || val <= 0) ? 1 : val; } // 统一判断EOF错误,便于回测数据结束或连接断开时优雅退出 function isEOFError(e) { var msg = ''; try { msg = (e && e.toString) ? e.toString() : String(e); } catch (_) { msg = ''; } return msg && msg.indexOf('EOF') !== -1; } // ==================== 仓位记录 ==================== const LONG_SIDE = 1; const SHORT_SIDE = -1; function addPosition(list, price, amount, side) { list.push({ price: price, amount: amount, side: side }); } function removePosition(list, idx) { list.splice(idx, 1); } // ==================== 全局变量 ==================== var config = null; var precision = null; // OKX ETH USDT 永续合约最小数量单位 0.01 var MIN_LOT_SIZE = 0.01; // 默认最小数量步长(OKX ETH 永续) var MIN_LOT_DECIMALS = 2; // 默认数量小数位(与步长匹配) var DEFAULT_PRICE_PREC = 1; // 默认价格精度 var forceMinLotSize = null; // 可选覆盖:最小步长 var forceAmountPrecision = null; // 可选覆盖:数量精度 function decimalPlaces(n) { var s = n.toString(); return s.indexOf('.') > -1 ? s.split('.')[1].length : 0; } // ==================== 交易规则获取(OKX API) ==================== function loadInstrumentSpec() { try { // 仅当交易所支持 IO 方式直连 OKX V5 时生效 var symbolParts = (config && config.symbol ? config.symbol : "ETH/USDT").replace("_", "/").split("/"); var base = symbolParts[0]; var quote = symbolParts[1]; var uly = base + "-" + quote; var params = "instType=SWAP&uly=" + encodeURIComponent(uly); var res = exchange.IO("api", "GET", "/api/v5/public/instruments", params); if (res && res.data && res.data.length > 0) { var inst = res.data[0]; if (inst.lotSz) { MIN_LOT_SIZE = parseFloat(inst.lotSz); MIN_LOT_DECIMALS = decimalPlaces(inst.lotSz); } if (inst.tickSz) { DEFAULT_PRICE_PREC = decimalPlaces(inst.tickSz); } Log("获取规则成功 lotSz:", inst.lotSz, "tickSz:", inst.tickSz); return true; } else { Log("获取规则失败:无数据返回"); } } catch (e) { Log("获取规则异常(可能托管者/交易所不支持 IO):", e); } return false; } // 应用用户覆盖的步长/精度(若提供)。若已获取到交易所规则,则忽略覆盖以防违规。 function applyForceLot() { if (config && config.forceMinLotSize && !isNaN(config.forceMinLotSize)) { var f = parseFloat(config.forceMinLotSize); // 只有在未成功获取交易所规则时才采用用户覆盖 if (!DEFAULT_PRICE_PREC || DEFAULT_PRICE_PREC === 1) { MIN_LOT_SIZE = f; MIN_LOT_DECIMALS = decimalPlaces(MIN_LOT_SIZE); Log("使用手动覆盖的最小步长:", MIN_LOT_SIZE, "小数位:", MIN_LOT_DECIMALS); } else { Log("已从交易所规则获取 lotSz,忽略手动最小步长覆盖"); } } if (config && config.forceAmountPrecision && !isNaN(config.forceAmountPrecision)) { forceAmountPrecision = parseInt(config.forceAmountPrecision, 10); Log("使用手动覆盖的数量精度:", forceAmountPrecision); } } // 多头相关 var MANY_BUYING = false; var MANY_NEXT_BUY_PRICE = 0; var MANY_ORDER_LIST = []; // 空头相关 var SHORT_BUYING = false; var SHORT_NEXT_BUY_PRICE = 0; var SHORT_ORDER_LIST = []; // 状态信息相关 var INIT_TIME = null; var INITIAL_BALANCE = 0; var LAST_PROFIT_TIME = null; var PROFIT_HISTORY = []; var CYCLE_START_TIME = 0; // 参数迁移相关 var oldConfig = null; var isMigrating = false; // 状态保存间隔(每5分钟保存一次) var LAST_SAVE_TIME = 0; var SAVE_INTERVAL = 300; // 5分钟 var LAST_RECONCILE_TIME = 0; var RECONCILE_INTERVAL = 60; // 每60秒对账一次 var RECONCILE_EMPTY_STREAK = 0; // 连续空结果计数,用于防抖 var RECONCILE_EMPTY_THRESHOLD = 2; // 连续2次空才清空本地持仓 // ==================== 下单函数 ==================== function placeOrder(dir, price, amount) { /** 下单函数,带错误处理和重试机制 */ try { exchange.SetDirection(dir); var finalPrice = safePrice(price); var finalAmount = safeAmount(amount); // 按交易所精度要求格式化价格与数量 var pricePrecision = (precision && precision.pricePrecision) ? precision.pricePrecision : 2; var amountPrecision = (precision && precision.amountPrecision) ? Math.max(precision.amountPrecision, MIN_LOT_DECIMALS) : (forceAmountPrecision || MIN_LOT_DECIMALS); finalPrice = formatPrice(finalPrice, pricePrecision); // 确保数量是0.01的整数倍(ETH永续合约最小单位) var minLotSize = MIN_LOT_SIZE; finalAmount = Math.round(finalAmount / minLotSize) * minLotSize; if (finalAmount < minLotSize) { finalAmount = minLotSize; } finalAmount = parseFloat(finalAmount.toFixed(amountPrecision)); // 按数量精度保留小数 // 确保数量至少是最小单位 if (finalAmount < minLotSize) { finalAmount = minLotSize; } var orderValue = finalPrice * finalAmount; Log("下单 - 方向:", dir, "价格:", finalPrice, "数量:", finalAmount, "金额:", orderValue.toFixed(2), "数量验证: 是0.01的", (finalAmount / minLotSize).toFixed(0), "倍"); var orderId = dir === 'buy' || dir === 'closebuy' ? exchange.Buy(finalPrice, finalAmount) : exchange.Sell(finalPrice, finalAmount); if (!orderId) { Log("下单失败 - 方向:", dir, "价格:", finalPrice, "数量:", finalAmount, "返回订单ID为空"); // 重试一次(如果数量不是最小单位的整数倍,尝试调整) if (finalAmount % minLotSize !== 0) { finalAmount = Math.ceil(finalAmount / minLotSize) * minLotSize; finalAmount = parseFloat(finalAmount.toFixed(2)); Log("下单重试 - 调整数量为:", finalAmount); orderId = dir === 'buy' || dir === 'closebuy' ? exchange.Buy(finalPrice, finalAmount) : exchange.Sell(finalPrice, finalAmount); } } else { Log("下单成功 - 方向:", dir, "订单ID:", orderId, "价格:", finalPrice, "数量:", finalAmount); } return orderId; } catch (e) { Log("下单异常 - 方向:", dir, "价格:", price, "数量:", amount, "错误:", e); return null; } } // 轮询订单成交,带超时和自定义回调,避免重复代码 function waitOrderFilled(orderId, timeoutSec, onFilled, onTimeout) { var deadline = Unix() + timeoutSec; while (true) { if (Unix() > deadline) { if (onTimeout) onTimeout(orderId); return false; } var order = exchange.GetOrder(orderId); if (order && (order.Status === 1 || order.Status === 2)) { onFilled(order); return true; } Sleep(100); } } // ==================== 多头网格策略 ==================== function firstManyBuy() { /** 首次多头买入,带超时和重试机制 */ if (MANY_BUYING) return; var ticker = exchange.GetTicker(); if (!ticker) { Log("首次多头买入失败:无法获取行情"); return; } var price = safePrice(ticker.Last); var orderValue = getOrderAmount(false); // 使用新参数 Log("准备首次多头买入 - 当前价格:", price, "目标金额:", orderValue); var amount = calculateOrderAmount(price, orderValue, precision.amountPrecision); Log("首次多头买入 - 计算出的数量:", amount, "价格:", price); var orderId = placeOrder('buy', price, amount); if (!orderId) { Log("首次多头买入失败:下单返回空,尝试重试..."); // 重试一次,使用调整后的数量 Sleep(1000); amount = calculateOrderAmount(price, orderValue, precision.amountPrecision); orderId = placeOrder('buy', price, amount); if (!orderId) { Log("首次多头买入重试失败:下单返回空"); return; } } Log("首次多头买入 - 订单ID:", orderId, "价格:", price, "数量:", amount); MANY_BUYING = true; var timeoutSec = 60; // 60秒超时 var filled = waitOrderFilled( orderId, timeoutSec, function(order) { var gridSpacing = getGridSpacing(LONG_SIDE, false); MANY_NEXT_BUY_PRICE = formatPrice( order.Price * (1 - gridSpacing / 100), precision.pricePrecision ); MANY_BUYING = false; addPosition(MANY_ORDER_LIST, order.Price, order.Amount, LONG_SIDE); Log("多头首次买入成功,价格:", order.Price, "数量:", order.Amount, "下一个买入价格:", MANY_NEXT_BUY_PRICE); saveHistory({ type: 'first_many_buy', price: order.Price, amount: order.Amount, value: order.Price * order.Amount }); safeSaveState(); }, function(orderIdTimeout) { Log("首次多头买入超时,取消订单:", orderIdTimeout); exchange.CancelOrder(orderIdTimeout); MANY_BUYING = false; } ); if (!filled) { return; } } function manyBuy() { /** 多头加仓(网格买入) */ if (MANY_BUYING) return; var ticker = exchange.GetTicker(); if (!ticker) { Log("多头加仓失败:无法获取行情"); return; } if (ticker.Last > MANY_NEXT_BUY_PRICE) { Log("多头加仓跳过:当前价格", ticker.Last, "高于下一个买入价格", MANY_NEXT_BUY_PRICE); return; } var price = safePrice(ticker.Last); var orderValue = getOrderAmount(false); // 使用新参数 Log("准备多头加仓 - 当前价格:", price, "下一个买入价格:", MANY_NEXT_BUY_PRICE, "目标金额:", orderValue); var amount = calculateOrderAmount(price, orderValue, precision.amountPrecision); Log("多头加仓 - 计算出的数量:", amount, "价格:", price); var orderId = placeOrder('buy', price, amount); if (!orderId) { Log("多头加仓失败:下单返回空"); return; } Log("多头加仓 - 订单ID:", orderId, "价格:", price, "数量:", amount); MANY_BUYING = true; var timeoutSec = 60 * 30; // 30分钟超时 var filled = waitOrderFilled( orderId, timeoutSec, function(order) { var gridSpacing = getGridSpacing(LONG_SIDE, false); MANY_NEXT_BUY_PRICE = formatPrice( order.Price * (1 - gridSpacing / 100), precision.pricePrecision ); MANY_BUYING = false; addPosition(MANY_ORDER_LIST, order.Price, order.Amount, LONG_SIDE); Log("多头加仓成功,价格:", order.Price, "数量:", order.Amount, "持仓数:", MANY_ORDER_LIST.length, "下一个买入价格:", MANY_NEXT_BUY_PRICE); saveHistory({ type: 'many_buy', price: order.Price, amount: order.Amount, value: order.Price * order.Amount }); safeSaveState(); }, function(orderIdTimeout) { exchange.CancelOrder(orderIdTimeout); MANY_BUYING = false; Log("多头加仓订单超时,已取消订单:", orderIdTimeout); } ); if (!filled) { return; } } function manySell() { /** 多头平仓(网格卖出)- 支持双轨制 */ var ticker = exchange.GetTicker(); if (!ticker) return; // 从最新持仓开始检查(倒序) for (var i = MANY_ORDER_LIST.length - 1; i >= 0; i--) { var pos = MANY_ORDER_LIST[i]; // 计算盈利价格(支持双轨制:旧持仓用旧参数,新持仓用新参数) var gridSpacing = getGridSpacing(LONG_SIDE, true); // 平仓时检查是否用旧参数 var profitPrice = formatPrice( pos.price * (1 + gridSpacing / 100), precision.pricePrecision ); // 如果当前价格达到盈利价格,平仓 if (ticker.Last >= profitPrice) { var position = exchange.GetPosition(); var longPos = null; if (position && position.length > 0) { for (var j = 0; j < position.length; j++) { if (position[j].Type === 0 && position[j].Amount > 0) { longPos = position[j]; break; } } } if (!longPos || longPos.Amount <= 0) break; var qty = safeAmount(Math.min(pos.amount, longPos.Amount)); // 确保数量是0.01的整数倍(ETH永续合约最小单位) var minLotSize = MIN_LOT_SIZE; qty = Math.floor(qty / minLotSize) * minLotSize; qty = parseFloat(qty.toFixed(2)); // 保留2位小数 if (qty < minLotSize) { Log("多头平仓跳过:数量太小", qty, "最小单位:", minLotSize); break; } Log("准备多头平仓 - 持仓价格:", pos.price, "当前价格:", ticker.Last, "盈利价格:", profitPrice, "平仓数量:", qty, "使用参数:", shouldUseOldConfig(LONG_SIDE) ? "旧参数" : "新参数"); // 多头平仓应使用 closesell(卖出平多) var orderId = placeOrder('closesell', ticker.Last, qty); if (!orderId) continue; while (true) { var order = exchange.GetOrder(orderId); if (!order) { Sleep(100); continue; } if (order.Status === 1 || order.Status === 2) { // 计算下一个买入价格(基于新参数的多头网格间距) var nextGridSpacing = getGridSpacing(LONG_SIDE, false); MANY_NEXT_BUY_PRICE = formatPrice( ticker.Last * (1 - nextGridSpacing / 100), precision.pricePrecision ); removePosition(MANY_ORDER_LIST, i); Log("多头平仓成功,价格:", order.Price, "数量:", order.Amount, "剩余持仓:", MANY_ORDER_LIST.length, "下一个买入价格:", MANY_NEXT_BUY_PRICE); // 保存历史记录 var profit = (order.Price - pos.price) * order.Amount; saveHistory({ type: 'many_sell', price: order.Price, amount: order.Amount, value: order.Price * order.Amount, entryPrice: pos.price, profit: profit }); // ✅ 成交后立即保存状态 safeSaveState(); // 检查迁移是否完成 checkMigrationComplete(); break; } Sleep(100); } } } } // ==================== 空头网格策略 ==================== function firstShortBuy() { /** 首次空头卖出,带超时和重试机制 */ if (SHORT_BUYING) return; var ticker = exchange.GetTicker(); if (!ticker) { Log("首次空头卖出失败:无法获取行情"); return; } var price = safePrice(ticker.Last); var orderValue = getOrderAmount(false); // 使用新参数 Log("准备首次空头卖出 - 当前价格:", price, "目标金额:", orderValue); var amount = calculateOrderAmount(price, orderValue, precision.amountPrecision); Log("首次空头卖出 - 计算出的数量:", amount, "价格:", price); var orderId = placeOrder('sell', price, amount); if (!orderId) { Log("首次空头卖出失败:下单返回空,尝试重试..."); // 重试一次,使用调整后的数量 Sleep(1000); amount = calculateOrderAmount(price, orderValue, precision.amountPrecision); orderId = placeOrder('sell', price, amount); if (!orderId) { Log("首次空头卖出重试失败:下单返回空"); return; } } Log("首次空头卖出 - 订单ID:", orderId, "价格:", price, "数量:", amount); SHORT_BUYING = true; var timeoutSec = 60; // 60秒超时 var filled = waitOrderFilled( orderId, timeoutSec, function(order) { var gridSpacing = getGridSpacing(SHORT_SIDE, false); SHORT_NEXT_BUY_PRICE = formatPrice( order.Price * (1 + gridSpacing / 100), precision.pricePrecision ); SHORT_BUYING = false; addPosition(SHORT_ORDER_LIST, order.Price, order.Amount, SHORT_SIDE); Log("空头首次卖出成功,价格:", order.Price, "数量:", order.Amount, "下一个卖出价格:", SHORT_NEXT_BUY_PRICE); saveHistory({ type: 'first_short_sell', price: order.Price, amount: order.Amount, value: order.Price * order.Amount }); safeSaveState(); }, function(orderIdTimeout) { Log("首次空头卖出超时,取消订单:", orderIdTimeout); exchange.CancelOrder(orderIdTimeout); SHORT_BUYING = false; } ); if (!filled) { return; } } function shortBuy() { /** 空头加仓(网格卖出) */ if (SHORT_BUYING) return; var ticker = exchange.GetTicker(); if (!ticker) { Log("空头加仓失败:无法获取行情"); return; } if (ticker.Last < SHORT_NEXT_BUY_PRICE) { Log("空头加仓跳过:当前价格", ticker.Last, "低于下一个卖出价格", SHORT_NEXT_BUY_PRICE); return; } var price = safePrice(ticker.Last); var orderValue = getOrderAmount(false); // 使用新参数 Log("准备空头加仓 - 当前价格:", price, "下一个卖出价格:", SHORT_NEXT_BUY_PRICE, "目标金额:", orderValue); var amount = calculateOrderAmount(price, orderValue, precision.amountPrecision); Log("空头加仓 - 计算出的数量:", amount, "价格:", price); var orderId = placeOrder('sell', price, amount); if (!orderId) { Log("空头加仓失败:下单返回空"); return; } Log("空头加仓 - 订单ID:", orderId, "价格:", price, "数量:", amount); SHORT_BUYING = true; var timeoutSec = 60 * 30; // 30分钟超时 var filled = waitOrderFilled( orderId, timeoutSec, function(order) { var gridSpacing = getGridSpacing(SHORT_SIDE, false); SHORT_NEXT_BUY_PRICE = formatPrice( order.Price * (1 + gridSpacing / 100), precision.pricePrecision ); SHORT_BUYING = false; addPosition(SHORT_ORDER_LIST, order.Price, order.Amount, SHORT_SIDE); Log("空头加仓成功,价格:", order.Price, "数量:", order.Amount, "持仓数:", SHORT_ORDER_LIST.length, "下一个卖出价格:", SHORT_NEXT_BUY_PRICE); saveHistory({ type: 'short_sell', price: order.Price, amount: order.Amount, value: order.Price * order.Amount }); safeSaveState(); }, function(orderIdTimeout) { exchange.CancelOrder(orderIdTimeout); SHORT_BUYING = false; Log("空头加仓订单超时,已取消订单:", orderIdTimeout); } ); if (!filled) { return; } } function shortSell() { /** 空头平仓(网格买入)- 支持双轨制 */ var ticker = exchange.GetTicker(); if (!ticker) return; // 从最新持仓开始检查(倒序) for (var i = SHORT_ORDER_LIST.length - 1; i >= 0; i--) { var pos = SHORT_ORDER_LIST[i]; // 计算盈利价格(支持双轨制:旧持仓用旧参数,新持仓用新参数) var gridSpacing = getGridSpacing(SHORT_SIDE, true); // 平仓时检查是否用旧参数 var profitPrice = formatPrice( pos.price * (1 - gridSpacing / 100), precision.pricePrecision ); // 如果当前价格达到盈利价格,平仓 if (ticker.Last <= profitPrice) { var position = exchange.GetPosition(); var shortPos = null; if (position && position.length > 0) { for (var j = 0; j < position.length; j++) { if (position[j].Type === 1 && position[j].Amount > 0) { shortPos = position[j]; break; } } } if (!shortPos || shortPos.Amount <= 0) break; var qty = safeAmount(Math.min(pos.amount, shortPos.Amount)); // 确保数量是0.01的整数倍(ETH永续合约最小单位) var minLotSize = MIN_LOT_SIZE; qty = Math.floor(qty / minLotSize) * minLotSize; qty = parseFloat(qty.toFixed(2)); // 保留2位小数 if (qty < minLotSize) { Log("空头平仓跳过:数量太小", qty, "最小单位:", minLotSize); break; } Log("准备空头平仓 - 持仓价格:", pos.price, "当前价格:", ticker.Last, "盈利价格:", profitPrice, "平仓数量:", qty, "使用参数:", shouldUseOldConfig(SHORT_SIDE) ? "旧参数" : "新参数"); // 空头平仓应使用 closebuy(买入平空) var orderId = placeOrder('closebuy', ticker.Last, qty); if (!orderId) continue; while (true) { var order = exchange.GetOrder(orderId); if (!order) { Sleep(100); continue; } if (order.Status === 1 || order.Status === 2) { // 计算下一个卖出价格(基于新参数的空头网格间距) var nextGridSpacing = getGridSpacing(SHORT_SIDE, false); SHORT_NEXT_BUY_PRICE = formatPrice( ticker.Last * (1 + nextGridSpacing / 100), precision.pricePrecision ); removePosition(SHORT_ORDER_LIST, i); Log("空头平仓成功,价格:", order.Price, "数量:", order.Amount, "剩余持仓:", SHORT_ORDER_LIST.length, "下一个卖出价格:", SHORT_NEXT_BUY_PRICE); // 保存历史记录 var profit = (pos.price - order.Price) * order.Amount; saveHistory({ type: 'short_buy', price: order.Price, amount: order.Amount, value: order.Price * order.Amount, entryPrice: pos.price, profit: profit }); // ✅ 成交后立即保存状态 safeSaveState(); // 检查迁移是否完成 checkMigrationComplete(); break; } Sleep(100); } } } } // ==================== 边界检查 ==================== function checkPriceBounds() { /** 检查价格是否在网格边界内 */ var ticker = exchange.GetTicker(); if (!ticker) return true; var currentPrice = ticker.Last; // 如果价格超出上边界,暂停多头买入 if (currentPrice >= config.upperPrice) { return false; } // 如果价格低于下边界,暂停空头卖出 if (currentPrice <= config.lowerPrice) { return false; } return true; } // ==================== 状态信息计算 ==================== function calculateLiquidationPrice(account, positions, leverage) { /** 计算爆仓价 */ var longLiquidationPrice = null; var shortLiquidationPrice = null; if (!account || !positions || positions.length === 0) { return { long: longLiquidationPrice, short: shortLiquidationPrice }; } var margin = account.Margin || 0; var balance = account.Balance || 0; var available = account.Available || 0; // 计算多头和空头持仓 var longAmount = 0; var longPrice = 0; var shortAmount = 0; var shortPrice = 0; for (var i = 0; i < positions.length; i++) { var pos = positions[i]; if (pos.Type === 0 && pos.Amount > 0) { // 多头 longAmount += pos.Amount; longPrice = pos.Price; } else if (pos.Type === 1 && pos.Amount > 0) { // 空头 shortAmount += pos.Amount; shortPrice = pos.Price; } } // 计算多头爆仓价(价格下跌导致爆仓) if (longAmount > 0 && longPrice > 0 && leverage > 0) { // 简化计算:爆仓价 = 持仓价 * (1 - 维持保证金率) // 维持保证金率通常为 0.5% - 1% var maintenanceMarginRate = 0.01; // 1% longLiquidationPrice = longPrice * (1 - maintenanceMarginRate); } // 计算空头爆仓价(价格上涨导致爆仓) if (shortAmount > 0 && shortPrice > 0 && leverage > 0) { var maintenanceMarginRate = 0.01; // 1% shortLiquidationPrice = shortPrice * (1 + maintenanceMarginRate); } return { long: longLiquidationPrice, short: shortLiquidationPrice }; } function calculateProfit(account, initialBalance) { /** 计算收益信息 */ if (!account || !initialBalance) { return { totalProfit: 0, totalProfitRate: 0, dailyProfit: 0, avgDailyReturn: 0, estimatedMonthlyReturn: 0, estimatedYearlyReturn: 0 }; } var currentBalance = account.Balance || 0; var totalProfit = currentBalance - initialBalance; var totalProfitRate = initialBalance > 0 ? (totalProfit / initialBalance) : 0; // 计算当日收益(简化处理,使用最近一次收益记录) var dailyProfit = 0; if (PROFIT_HISTORY.length > 0) { var today = new Date(); today.setHours(0, 0, 0, 0); var todayTimestamp = Math.floor(today.getTime() / 1000); var yesterdayProfit = 0; for (var i = PROFIT_HISTORY.length - 1; i >= 0; i--) { if (PROFIT_HISTORY[i].time < todayTimestamp) { yesterdayProfit = PROFIT_HISTORY[i].profit; break; } } dailyProfit = totalProfit - yesterdayProfit; } // 计算平均日化收益率 var avgDailyReturn = 0; var estimatedMonthlyReturn = 0; var estimatedYearlyReturn = 0; if (INIT_TIME && INIT_TIME > 0) { var days = (Unix() - INIT_TIME) / (24 * 3600); if (days > 0) { avgDailyReturn = totalProfitRate / days; estimatedMonthlyReturn = avgDailyReturn * 30; estimatedYearlyReturn = avgDailyReturn * 365; } } return { totalProfit: totalProfit, totalProfitRate: totalProfitRate, dailyProfit: dailyProfit, avgDailyReturn: avgDailyReturn, estimatedMonthlyReturn: estimatedMonthlyReturn, estimatedYearlyReturn: estimatedYearlyReturn }; } function getPositionInfo() { /** 获取持仓详细信息 */ var positions = []; var ticker = exchange.GetTicker(); var currentPrice = ticker ? ticker.Last : 0; // 获取实际持仓 var exchangePositions = exchange.GetPosition(); if (!exchangePositions || exchangePositions.length === 0) { return positions; } for (var i = 0; i < exchangePositions.length; i++) { var pos = exchangePositions[i]; if (pos.Amount <= 0) continue; var direction = pos.Type === 0 ? 'long' : 'short'; var positionValue = pos.Amount * pos.Price; var unrealizedPnl = pos.Profit || 0; // 计算预估回归收益(基于网格间距) var gridSpacing = direction === 'long' ? config.longGridSpacing : config.shortGridSpacing; var estimatedReturn = positionValue * (gridSpacing / 100); var estimatedTotalReturn = estimatedReturn * (direction === 'long' ? MANY_ORDER_LIST.length : SHORT_ORDER_LIST.length); // 网格初始价格和转向间距 var gridInitialPrice = currentPrice; var turnSpacing = gridSpacing; // 挂单价格 var buyOrderPrice = direction === 'long' ? MANY_NEXT_BUY_PRICE : 0; var sellOrderPrice = direction === 'short' ? SHORT_NEXT_BUY_PRICE : 0; // 计算成交额(简化处理) var tradeVolume = positionValue; positions.push({ symbol: config.symbol, direction: direction, quantity: pos.Amount, positionPrice: pos.Price, positionValue: positionValue, estimatedReturn: estimatedReturn, estimatedTotalReturn: estimatedTotalReturn, gridInitialPrice: gridInitialPrice, turnSpacing: turnSpacing, currentPrice: currentPrice, buyOrderPrice: buyOrderPrice, sellOrderPrice: sellOrderPrice, unrealizedPnl: unrealizedPnl, tradeVolume: tradeVolume }); } return positions; } function updateStatusInfo() { /** 更新并输出状态信息(FMZ原生表格格式,框架式布局) */ try { var account = exchange.GetAccount(); var positions = exchange.GetPosition(); var ticker = exchange.GetTicker(); if (!account) return; // 计算爆仓价 var leverage = 1; var liquidationPrices = calculateLiquidationPrice(account, positions, leverage); // 计算收益 var profitInfo = calculateProfit(account, INITIAL_BALANCE); // 计算循环延时 var cycleDelay = 0; if (CYCLE_START_TIME > 0) { cycleDelay = (Unix() - CYCLE_START_TIME) * 1000; } CYCLE_START_TIME = Unix(); // 获取持仓信息 var positionInfo = getPositionInfo(); // 格式化时间 var initTimeStr = '-'; if (INIT_TIME) { var initDate = new Date(INIT_TIME * 1000); var pad = function(n) { return n < 10 ? '0' + n : n; }; initTimeStr = initDate.getFullYear() + '-' + pad(initDate.getMonth() + 1) + '-' + pad(initDate.getDate()) + ' ' + pad(initDate.getHours()) + ':' + pad(initDate.getMinutes()) + ':' + pad(initDate.getSeconds()); } // 计算运行时长 var runTime = '-'; if (INIT_TIME) { var seconds = Unix() - INIT_TIME; var days = Math.floor(seconds / 86400); var hours = Math.floor((seconds % 86400) / 3600); var minutes = Math.floor((seconds % 3600) / 60); runTime = days + '天' + hours + '小时' + minutes + '分钟'; } // 构造纯文本表格(按行输出),在移动端也能清晰显示 var lines = []; var addTextSection = function(title, rows) { lines.push(title); lines.push('字段 | 值'); lines.push('---- | ----'); for (var i = 0; i < rows.length; i++) { lines.push(rows[i][0] + ' | ' + rows[i][1]); } lines.push(''); // 空行分隔 }; addTextSection('📊 基础', [ ['初始化时间', initTimeStr], ['运行时长', runTime], ['当前价格', ticker ? ticker.Last.toFixed(2) : '-'], ['做多爆仓价', liquidationPrices.long ? liquidationPrices.long.toFixed(2) : '-'], ['做空爆仓价', liquidationPrices.short ? liquidationPrices.short.toFixed(2) : '-'], ['参数迁移', isMigrating ? '迁移中' : '正常'], isMigrating && oldConfig ? ['旧多头间距', oldConfig.longGridSpacing + '%'] : null, isMigrating && oldConfig ? ['旧空头间距', oldConfig.shortGridSpacing + '%'] : null ].filter(Boolean)); addTextSection('💰 账户', [ ['初始余额', INITIAL_BALANCE.toFixed(2)], ['钱包余额', (account.Balance || 0).toFixed(2)], ['保证金余额', (account.Margin || 0).toFixed(2)], ['可用保证金', (account.Available || 0).toFixed(2)], ['已用杠杆', leverage + 'x'] ]); addTextSection('📈 收益', [ ['总收益', profitInfo.totalProfit.toFixed(2)], ['总收益率', (profitInfo.totalProfitRate * 100).toFixed(2) + '%'], ['当日收益', profitInfo.dailyProfit.toFixed(2)], ['平均日化', (profitInfo.avgDailyReturn * 100).toFixed(2) + '%'], ['预估月化', (profitInfo.estimatedMonthlyReturn * 100).toFixed(2) + '%'], ['预估年化', (profitInfo.estimatedYearlyReturn * 100).toFixed(2) + '%'] ]); addTextSection('🔲 网格', [ ['多头持仓数', MANY_ORDER_LIST.length], ['下一个买入价', MANY_NEXT_BUY_PRICE > 0 ? MANY_NEXT_BUY_PRICE.toFixed(2) : '-'], ['多头间距', config.longGridSpacing + '%'], ['空头持仓数', SHORT_ORDER_LIST.length], ['下一个卖出价', SHORT_NEXT_BUY_PRICE > 0 ? SHORT_NEXT_BUY_PRICE.toFixed(2) : '-'], ['空头间距', config.shortGridSpacing + '%'], ['单网格金额', config.orderAmount.toFixed(2)] ]); var posRows = []; if (positionInfo.length > 0) { for (var i = 0; i < positionInfo.length; i++) { var pos = positionInfo[i]; posRows.push(['持仓' + (i + 1) + ' (' + (pos.direction === 'long' ? '多' : '空') + ')', '']); posRows.push(['数量', pos.quantity.toFixed(2)]); posRows.push(['持仓价', pos.positionPrice.toFixed(2)]); posRows.push(['当前价', pos.currentPrice.toFixed(2)]); posRows.push(['持仓价值', pos.positionValue.toFixed(2)]); posRows.push(['未实现盈亏', pos.unrealizedPnl.toFixed(2)]); if (i < positionInfo.length - 1) { posRows.push(['---', '---']); } } } else { posRows.push(['暂无持仓', '']); } addTextSection('📦 持仓', posRows); addTextSection('⚙️ 系统', [ ['循环延时', cycleDelay.toFixed(0) + 'ms'], ['状态保存', LAST_SAVE_TIME > 0 ? new Date(LAST_SAVE_TIME * 1000).toLocaleTimeString() : '-'], ['收益记录数', PROFIT_HISTORY.length] ]); LogStatus(lines.join('\n')); // 记录收益曲线(使用LogProfit,FMZ平台会自动显示收益曲线) var currentProfit = profitInfo.totalProfit; LogProfit(currentProfit); // 保存收益历史 PROFIT_HISTORY.push({ time: Unix(), profit: currentProfit }); // 只保留最近1000条记录 if (PROFIT_HISTORY.length > 1000) { PROFIT_HISTORY.shift(); } // 定期保存状态(每5分钟) if (Unix() - LAST_SAVE_TIME >= SAVE_INTERVAL) { safeSaveState(); } } catch (e) { Log("更新状态信息失败:", e); } } // ==================== 主循环 ==================== function onTick() { var ticker = exchange.GetTicker(); if (!ticker) return; // 定期持仓对账,防止本地列表与实际仓位不一致(尤其防止空头被抵消后仍显示) if (Unix() - LAST_RECONCILE_TIME >= RECONCILE_INTERVAL) { reconcilePositions(); LAST_RECONCILE_TIME = Unix(); } // 检查价格边界 if (!checkPriceBounds()) { Log("价格超出网格边界,暂停交易"); return; } // 检查参数迁移是否完成 checkMigrationComplete(); // 如果多头持仓为空,首次买入 if (MANY_ORDER_LIST.length === 0) { firstManyBuy(); } else { // 多头加仓和平仓 manyBuy(); manySell(); } // 如果空头持仓为空,首次卖出 if (SHORT_ORDER_LIST.length === 0) { firstShortBuy(); } else { // 空头加仓和平仓 shortBuy(); shortSell(); } // 更新状态信息 updateStatusInfo(); } // ==================== 主函数 ==================== function main() { // 设置交易所类型(OKX) exchange.SetContractType("swap"); // 或 "spot" 根据需求 // 尝试设置双向持仓(对冲)模式,若不支持则忽略,避免 TypeError if (typeof exchange.SetPositionMode === 'function') { try { exchange.SetPositionMode(1); Log("已尝试设置对冲模式 (SetPositionMode=1)"); } catch (e) { Log("设置对冲模式失败,继续运行(可能交易所/托管者不支持):", e); } } else { Log("SetPositionMode 不可用,跳过对冲模式设置"); } // ✅ 从FMZ界面获取配置参数(禁止硬编码) // 注意:FMZ平台中,界面设置的参数名称会自动成为JavaScript全局变量 config = getConfigParams(); // 尝试获取交易规则(tickSz/lotSz),以便自动设置步长与精度 loadInstrumentSpec(); // 应用用户手动覆盖(如有) applyForceLot(); // 最终将精度写回 precision 供后续使用 precision = getPrecision(); // 验证配置 if (!config.symbol) { throw new Error("请设置币种选择(界面参数名:symbol)"); } if (!config.upperPrice || !config.lowerPrice) { throw new Error("请设置网格上边界价格和下边界价格(界面参数名:upperPrice, lowerPrice)"); } if (config.upperPrice <= config.lowerPrice) { throw new Error("上边界价格必须大于下边界价格"); } if (!config.orderAmount || config.orderAmount <= 0) { throw new Error("单个网格下单金额必须大于0(界面参数名:orderAmount)"); } if (!config.longGridSpacing || config.longGridSpacing <= 0) { throw new Error("多头网格间距必须大于0(界面参数名:longGridSpacing)"); } if (!config.shortGridSpacing || config.shortGridSpacing <= 0) { throw new Error("空头网格间距必须大于0(界面参数名:shortGridSpacing)"); } // 尝试恢复状态 Log("========== 策略启动 =========="); var stateRestored = loadState(); if (stateRestored) { Log("✅ 状态恢复成功"); } else { Log("ℹ️ 首次运行,使用初始状态"); } // 检查配置变化(启动参数迁移检测) checkConfigChange(config); // 获取精度 precision = getPrecision(); Log("========== 策略配置 =========="); Log("币种选择:", config.symbol); Log("多头网格间距:", config.longGridSpacing, "%"); Log("空头网格间距:", config.shortGridSpacing, "%"); Log("单个网格下单金额:", config.orderAmount); Log("网格上边界价格:", config.upperPrice); Log("网格下边界价格:", config.lowerPrice); Log("价格精度:", precision.pricePrecision); Log("数量精度:", precision.amountPrecision); Log("合约最小单位: 0.01 (ETH永续合约)"); Log("============================"); // 初始化价格(如果状态未恢复) if (!stateRestored) { var ticker = exchange.GetTicker(); if (!ticker || !ticker.Last || ticker.Last <= 0) { throw new Error("无法获取初始价格"); } var initPrice = ticker.Last; // 初始化多头和空头的下一个买入/卖出价格 MANY_NEXT_BUY_PRICE = formatPrice( initPrice * (1 - config.longGridSpacing / 100), precision.pricePrecision ); SHORT_NEXT_BUY_PRICE = formatPrice( initPrice * (1 + config.shortGridSpacing / 100), precision.pricePrecision ); Log("初始价格:", initPrice); Log("多头下一个买入价格:", MANY_NEXT_BUY_PRICE); Log("空头下一个卖出价格:", SHORT_NEXT_BUY_PRICE); // 初始化状态信息 INIT_TIME = Unix(); var account = exchange.GetAccount(); if (account) { INITIAL_BALANCE = account.Balance || 0; Log("初始余额:", INITIAL_BALANCE); } // 初始化收益记录 LogProfit(0); } else { Log("使用恢复的状态继续运行"); } CYCLE_START_TIME = Unix(); LAST_SAVE_TIME = Unix(); // 保存初始状态 safeSaveState(); Log("========== 策略运行中 =========="); Log("状态将每5分钟自动保存,支持关机/重启后恢复"); // 主循环 while (true) { try { onTick(); } catch (e) { if (isEOFError(e)) { Log("检测到 EOF,可能是回测数据结束或连接被关闭,保存状态后退出主循环"); safeSaveState(); break; } Log("策略执行错误:", e); } Sleep(3000); // 每3秒执行一次 } }
Related Recommendations
Comment
All comments (11)
    javascript
    function main(){ const marketinfo = exchange.GetMarkets() const symbolinfo = marketinfo['BTC_USDT.swap'] Log(symbolinfo) //返回币种的各种信息 AmountPrecision 为3 let quantity = 5.776123 quantity = _N(quantity, symbolinfo.AmountPrecision) Log(quantity) //返回数量精度处理后的 5.776 }
    7 months ago
    javascript
    let statusDisplay = '`' + JSON.stringify(accountTable) + '`\n\n'; statusDisplay += '`' + JSON.stringify(positionTable) + '`\n\n'; statusDisplay += '`' + JSON.stringify(analysisTable) + '`\n\n'; statusDisplay += '`' + JSON.stringify(executionTable) + '`'; // 添加更新时间提示 if (lastUpdateTime) { const timeSinceUpdate = Math.floor((Date.now() - lastUpdateTime) / 1000); statusDisplay += `\n\n⏱️ 上次决策: ${timeSinceUpdate}秒前`; } // 显示状态 LogStatus(statusDisplay);

    参考一下

    6 months ago

    看起来问题挺多的,可以扔给AI,审核一下策略代码。

    7 months ago

    ETH在欧易下单精度可以0.001就这一个问题我熬夜五天到现在都不能解决AI也不能解决

    7 months ago

    扔给AI AI就骗我。半个月了AI我都换冒烟了,还是不能解决。还的是人工,求助各位大佬

    7 months ago

    代码看着有点像AI写的。

    7 months ago

    是的梦老板,我怎么都弄不好下单精度还有多空网格同时运行出现混乱的问题,弄了半个月了太难了

    7 months ago

    精度计算中的变量作用域问题

    javascript
    function calculateOrderAmount(price, orderValue, amountPrecision) { // ... Log("计算订单数量 - 对齐到步长: " + amount.toFixed(amtPrecision)); // ↑ amtPrecision 在这行之后才定义 var amtPrecision = Math.max(amountPrecision || ...); }

    amtPrecision 在使用前未定义,JavaScript会变量提升但值为 undefined。

    7 months ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)