9
ফোকাস
16
অনুসারী

চিরস্থায়ী সীমাহীন গ্রিড চুক্তির (দীর্ঘ এবং স্বল্প পজিশন) অর্ডারের নির্ভুলতা এবং বাস্তবায়ন নিয়ে আমার সমস্যা হচ্ছে। আমি অর্ধ মাস ধরে এটি নিয়ে কাজ করছি এবং আমি সম্পূর্ণরূপে আটকে আছি। আমি সমাধানের জন্...

তৈরি: 2025-12-08 11:00:34, আপডেট করা হয়েছে: 2025-12-08 11:27:50
comments   11
hits   511

”`js /*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('📈 收