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秒执行一次
}
} “`