滚单策略


创建日期: 2025-12-19 14:01:50 最后修改: 2025-12-22 15:26:38
复制: 16 点击次数: 165
avatar of ianzeng123 ianzeng123
2
关注
350
关注者
策略源码
/*backtest
start: 2025-01-20 00:00:00
end: 2025-01-21 00:00:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_Binance","currency":"TRUMP_USDT","balance":5000}]
*/

// ============================================
// 滚仓策略 - EMA5/EMA10 简化版
// 使用 CreateOrder 统一下单
// 持续检测订单状态
// 止盈后根据EMA关系决定是否滚仓
// 新增:滚仓统计功能(三个两行表格)
// 修复:方向记录、亏损记录、入场价格记录
// 优化:市价平仓循环重试直到成功
// 优化:滚仓统计表格新增开始/结束时间
// ============================================

// ========== 策略参数(可调整)==========
var Symbol = "TRUMP_USDT.swap";      // 交易币种
var InitialCapital = 100;            // 策略初始资金 100U
var Leverage = 3;                    // 杠杆倍数
var RollProfitPercent = 0.10;        // 滚仓盈利系数(10% = 0.10)
var StopLossPercent = 0.05;          // 止损系数(10% = 0.10)

// EMA参数
var FastEMA = 5;
var SlowEMA = 10;

// 全局变量
var strategyCapital = InitialCapital;
var entryPrice = 0;
var lastRollPrice = 0;
var rollCount = 0;
var totalProfitRealized = 0;
var currentDirection = "";
var takeProfitOrderId = null;        // 止盈单ID
var amountPrecision = 0;             // 数量精度
var pricePrecision = 2;              // 价格精度
var ctVal = 1;                       // 合约面值

// ========== 滚仓统计变量 ==========
var currentRoundRolls = 0;           // 本轮滚仓次数(连续滚仓)
var currentRoundStartTime = 0;       // 本轮开始时间
var currentRoundDirection = "";      // 本轮方向
var currentRoundTotalProfit = 0;     // 本轮累计盈利(每次止盈累加)
var currentRoundLoss = 0;            // 本轮亏损(止损时记录)
var currentRoundEntryPrice = 0;      // 本轮入场价格
var rollHistory = [];                // 滚仓历史记录
var maxHistoryRecords = 10;          // 保留最近10次滚仓记录

function main() {
    Log("=== EMA滚仓策略启动(CreateOrder模式 + 滚仓统计)===");
    Log("交易币种:", Symbol);
    Log("━━━━━━━━━━━━━━━━━━━━");
    
    // 获取市场信息
    var markets = exchange.GetMarkets();
    if (!markets || !markets[Symbol]) {
        Log("❌ 错误:无法获取", Symbol, "的市场信息");
        return;
    }
    
    var marketInfo = markets[Symbol];
    amountPrecision = marketInfo.AmountPrecision;
    pricePrecision = marketInfo.PricePrecision || 2;
    ctVal = marketInfo.CtVal;
    
    Log("市场信息:");
    Log("  - 数量精度:", amountPrecision);
    Log("  - 价格精度:", pricePrecision);
    Log("  - 合约面值:", ctVal);
    
    var account = _C(exchange.GetAccount);
    Log("账户总资金:", account.Balance.toFixed(2), "U");
    Log("策略使用资金:", InitialCapital, "U");
    Log("杠杆倍数:", Leverage, "倍");
    Log("滚仓系数:", (RollProfitPercent * 100), "%");
    Log("止损系数:", (StopLossPercent * 100), "%");
    Log("━━━━━━━━━━━━━━━━━━━━");
    
    if (account.Balance < InitialCapital) {
        Log("❌ 错误:账户余额不足");
        return;
    }
    
    exchange.SetContractType("swap");
    exchange.SetMarginLevel(Leverage);
    
    var lastBarTime = 0;
    
    while (true) {
        
        var records = _C(exchange.GetRecords, PERIOD_M1);
        
        if (records.length < SlowEMA + 5) {
            Sleep(3000);
            continue;
        }
        
        var currentBarTime = records[records.length - 1].Time;
        if (currentBarTime == lastBarTime) {
            Sleep(1000);
            continue;
        }
        lastBarTime = currentBarTime;
        
        var ticker = _C(exchange.GetTicker);
        var currentPrice = ticker.Last;
        var account = _C(exchange.GetAccount);
        var position = _C(exchange.GetPositions); 
        
        // 计算EMA
        var emaFast = TA.EMA(records, FastEMA);
        var emaSlow = TA.EMA(records, SlowEMA);
        
        if (emaFast.length < 3 || emaSlow.length < 3) {
            Sleep(3000);
            continue;
        }
        
        var ema5_current = emaFast[emaFast.length - 1];
        var ema5_prev = emaFast[emaFast.length - 2];
        var ema10_current = emaSlow[emaSlow.length - 1];
        var ema10_prev = emaSlow[emaSlow.length - 2];
        
        var isBullTrend = ema5_current > ema10_current;
        var isBearTrend = ema5_current < ema10_current;
        
        var bullCross = ema5_prev <= ema10_prev && ema5_current > ema10_current;
        var bearCross = ema5_prev >= ema10_prev && ema5_current < ema10_current;

        if(takeProfitOrderId){
            checkTakeProfitOrder();
        }
        
        // ========== 持仓逻辑 ==========
        if (position.length > 0) {
            var pos = position[0];
            currentDirection = pos.Type == PD_LONG ? "LONG" : "SHORT";
            
            if (entryPrice == 0) {
                entryPrice = pos.Price;
                lastRollPrice = pos.Price;
            }
            
            // 检查止损
            checkStopLoss(currentPrice, pos);
            
        } else {
            // ========== 空仓:等待信号 ==========
            if (bullCross) {
                Log("📈 金叉信号 - 做多");
                openPosition("LONG", currentPrice);
            } else if (bearCross) {
                Log("📉 死叉信号 - 做空");
                openPosition("SHORT", currentPrice);
            }
        }
        
        showStatus(account, position, currentPrice, ema5_current, ema10_current, isBullTrend, currentBarTime);
        
        Sleep(1000);
    }
}

// 开仓(持续检测订单状态)
function openPosition(direction, price) {
    Log("🚀 开仓", direction == "LONG" ? "做多" : "做空");
    Log("使用资金:", strategyCapital.toFixed(2), "U");
    
    var positionValue = strategyCapital * Leverage;
    var amount = _N(positionValue / price / ctVal, amountPrecision);
    
    Log("计算数量:", amount, "| 持仓价值:", positionValue.toFixed(2), "U");
    
    if (amount <= 0) {
        Log("❌ 数量无效");
        return false;
    }
    
    // 使用 CreateOrder 市价开仓
    var orderId = exchange.CreateOrder(Symbol, direction == "LONG" ? "buy" : "sell", -1, amount);
    
    if (!orderId) {
        Log("❌ 下单失败");
        return false;
    }
    
    Log("订单ID:", orderId, "开始持续检测...");
    
    // 持续检测订单状态,直到成交或超时
    var maxWaitTime = 30000;  // 最多等待30秒
    var startTime = Date.now();
    var checkCount = 0;
    
    while (Date.now() - startTime < maxWaitTime) {
        Sleep(500);
        checkCount++;
        
        var order = exchange.GetOrder(orderId);
        if (!order) {
            Log("❌ 无法获取订单信息");
            continue;
        }
        
        if (order.Status == 1) {
            // 订单已成交
            var avgPrice = order.AvgPrice;
            entryPrice = avgPrice;
            lastRollPrice = avgPrice;
            currentDirection = direction;
            
            // ========== 修改:无论是否第一次,都要初始化/更新统计数据 ==========
            if (currentRoundRolls == 0) {
                // 第一次开仓:初始化所有统计数据
                currentRoundStartTime = Date.now();
                currentRoundDirection = direction;
                currentRoundTotalProfit = 0;
                currentRoundLoss = 0;
                currentRoundEntryPrice = avgPrice;
                Log("🆕 开始新一轮交易统计");
                Log("  - 开始时间:", _D(currentRoundStartTime));
                Log("  - 方向:", direction == "LONG" ? "🟢 多头" : "🔴 空头");
                Log("  - 入场价格:", avgPrice.toFixed(pricePrecision));
            } else {
                // 滚仓时:更新方向(理论上应该相同,但为了健壮性还是更新)
                currentRoundDirection = direction;
                Log("🔄 滚仓操作 (第", currentRoundRolls, "次)");
                Log("  - 方向:", direction == "LONG" ? "🟢 多头" : "🔴 空头");
                Log("  - 入场价格:", avgPrice.toFixed(pricePrecision));
            }
            
            Log("✅ 开仓成功!");
            Log("  - 成交均价:", avgPrice.toFixed(pricePrecision));
            Log("  - 成交数量:", order.DealAmount);
            Log("  - 成交金额:", (order.DealAmount * avgPrice * ctVal).toFixed(2), "U");
            
            // 挂止盈单
            Sleep(1000);
            placeTakeProfitOrder(direction, avgPrice, order.DealAmount);
            
            return true;
        } else if (order.Status == 2) {
            // 订单已取消
            Log("❌ 订单已取消");
            return false;
        }
        // Status == 0 表示未成交,继续等待
    }
    
    // 超时未成交
    Log("⚠️ 订单超时,尝试取消订单");
    exchange.CancelOrder(orderId);
    return false;
}

// 挂止盈单
function placeTakeProfitOrder(direction, entryPrice, amount) {
    var takeProfitPrice = 0;
    
    if (direction == "LONG") {
        takeProfitPrice = _N(entryPrice * 1.1, pricePrecision);  // 多头止盈:+10%
    } else {
        takeProfitPrice = _N(entryPrice * 0.9, pricePrecision);  // 空头止盈:-10%
    }
    
    Log("📌 挂止盈单");
    Log("  - 入场价格:", entryPrice.toFixed(pricePrecision));
    Log("  - 止盈价格:", takeProfitPrice);
    Log("  - 数量:", amount);
    
    // 使用 CreateOrder 挂限价止盈单
    if (direction == "LONG") {
        takeProfitOrderId = exchange.CreateOrder(Symbol, "closebuy", takeProfitPrice, amount);
    } else {
        takeProfitOrderId = exchange.CreateOrder(Symbol, "closesell", takeProfitPrice, amount);
    }
    
    if (takeProfitOrderId) {
        Log("✅ 止盈单已挂,订单ID:", takeProfitOrderId);
    } else {
        Log("❌ 止盈单挂单失败");
    }
}

// 检查止盈单状态
function checkTakeProfitOrder() {

    if (!takeProfitOrderId) {
        return;
    }
    
    var order = exchange.GetOrder(takeProfitOrderId);
    if (!order) {
        return;
    }
    
    if (order.Status == 1) {
        // 止盈单成交
        Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        Log("💰 止盈单成交!");
        Log("  - 成交价格:", order.AvgPrice.toFixed(pricePrecision));
        Log("  - 成交数量:", order.DealAmount);
        
        // 使用订单数据精确计算盈利
        var profit = 0;
        if (currentDirection == "LONG") {
            // 多头盈利 = (止盈价 - 入场价) * 数量 * 合约面值
            profit = (order.AvgPrice - entryPrice) * order.DealAmount * ctVal;
        } else {
            // 空头盈利 = (入场价 - 止盈价) * 数量 * 合约面值
            profit = (entryPrice - order.AvgPrice) * order.DealAmount * ctVal;
        }
        
        // 计算盈利率
        var profitRate = profit / strategyCapital;
        
        Log("📊 盈利统计:");
        Log("  - 入场价格:", entryPrice.toFixed(pricePrecision));
        Log("  - 止盈价格:", order.AvgPrice.toFixed(pricePrecision));
        Log("  - 本次盈利:", profit.toFixed(2), "U");
        Log("  - 盈利率:", (profitRate * 100).toFixed(2), "%");
        Log("  - 策略资金(盈利前):", strategyCapital.toFixed(2), "U");
        
        // 更新资金
        strategyCapital += profit;
        totalProfitRealized += profit;
        rollCount++;
        
        Log("  - 策略资金(盈利后):", strategyCapital.toFixed(2), "U");
        Log("  - 累计盈利:", totalProfitRealized.toFixed(2), "U");
        Log("  - 滚仓次数:", rollCount, "次");
        
        // ========== 累加本轮盈利 ==========
        currentRoundTotalProfit += profit;
        Log("  - 本轮累计盈利:", currentRoundTotalProfit.toFixed(2), "U");
        
        // 重置止盈单ID
        takeProfitOrderId = null;
        
        // 获取最新K线计算EMA
        Sleep(1000);
        var records = _C(exchange.GetRecords, PERIOD_M1);
        var emaFast = TA.EMA(records, FastEMA);
        var emaSlow = TA.EMA(records, SlowEMA);
        
        if (emaFast.length < 2 || emaSlow.length < 2) {
            Log("⚠️ EMA数据不足,无法判断是否滚仓");
            
            // 记录本轮滚仓结束(正常结束,之前有盈利)
            saveRollRecord(false);
            
            resetPositionState();
            Log("⏳ 等待新信号...");
            Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            return;
        }
        
        var ema5_current = emaFast[emaFast.length - 1];
        var ema10_current = emaSlow[emaSlow.length - 1];
        
        Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        Log("📈 EMA滚仓判断:");
        Log("  - EMA5:", ema5_current.toFixed(pricePrecision));
        Log("  - EMA10:", ema10_current.toFixed(pricePrecision));
        Log("  - 原持仓方向:", currentDirection);
        
        var shouldRoll = false;
        
        if (currentDirection == "LONG") {
            // 多头止盈后,如果EMA5仍在EMA10上方,继续做多(滚仓)
            if (ema5_current > ema10_current) {
                shouldRoll = true;
                Log("  - 判断结果: ✅ EMA5 > EMA10,趋势延续");
                Log("  - 决策: 🔄 继续做多(滚仓)");
            } else {
                Log("  - 判断结果: ❌ EMA5 <= EMA10,趋势转弱");
                Log("  - 决策: ⏸️ 不滚仓,等待新信号");
            }
        } else if (currentDirection == "SHORT") {
            // 空头止盈后,如果EMA5仍在EMA10下方,继续做空(滚仓)
            if (ema5_current < ema10_current) {
                shouldRoll = true;
                Log("  - 判断结果: ✅ EMA5 < EMA10,趋势延续");
                Log("  - 决策: 🔄 继续做空(滚仓)");
            } else {
                Log("  - 判断结果: ❌ EMA5 >= EMA10,趋势转弱");
                Log("  - 决策: ⏸️ 不滚仓,等待新信号");
            }
        }
        
        Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        
        if (shouldRoll) {
            // ========== 滚仓:增加本轮滚仓次数 ==========
            currentRoundRolls++;
            Log("🔄 执行滚仓操作... (本轮第", currentRoundRolls, "次滚仓)");
            Sleep(1000);
            
            var ticker = _C(exchange.GetTicker);
            var newPrice = ticker.Last;
            
            if (openPosition(currentDirection, newPrice)) {
                Log("✅ 滚仓成功!");
            } else {
                Log("❌ 滚仓失败,等待新信号");
                // 记录本轮滚仓结束(滚仓失败,但之前有盈利)
                saveRollRecord(false);
                resetPositionState();
            }
        } else {
            // ========== 不滚仓:记录本轮滚仓结束 ==========
            saveRollRecord(false);
            resetPositionState();
            Log("⏳ 已平仓,等待新信号...");
        }
        
        Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    }
}

// ========== 保存滚仓记录 ==========
function saveRollRecord(isStopLoss) {
    // 必须有开始时间才记录(防止异常情况)
    if (currentRoundStartTime == 0) {
        Log("⚠️ 本轮未正确初始化,跳过记录");
        currentRoundRolls = 0;
        currentRoundTotalProfit = 0;
        currentRoundLoss = 0;
        currentRoundDirection = "";
        currentRoundEntryPrice = 0;
        return;
    }
    
    var endTime = Date.now();
    var duration = endTime - currentRoundStartTime;
    
    // 计算总体盈利 = 累计盈利 - 亏损
    var netProfit = currentRoundTotalProfit - currentRoundLoss;
    
    var record = {
        direction: currentRoundDirection,      // 本轮方向
        roundRolls: currentRoundRolls,        // 本轮滚仓次数
        totalProfit: currentRoundTotalProfit, // 累计盈利(止盈累加)
        loss: currentRoundLoss,               // 亏损金额(止损)
        netProfit: netProfit,                 // 总体盈利
        duration: duration,                   // 持续时间(毫秒)
        isStopLoss: isStopLoss,              // 是否止损结束
        startTime: currentRoundStartTime,     // 开始时间
        endTime: endTime,                     // 结束时间
        entryPrice: currentRoundEntryPrice    // 入场价格
    };
    
    rollHistory.push(record);
    
    // 只保留最近N条记录
    if (rollHistory.length > maxHistoryRecords) {
        rollHistory.shift();
    }
    
    Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    Log("📝 保存滚仓记录:");
    Log("  - 方向:", currentRoundDirection == "LONG" ? "🟢 多头" : "🔴 空头");
    Log("  - 入场价格:", currentRoundEntryPrice.toFixed(pricePrecision));
    Log("  - 开始时间:", _D(currentRoundStartTime));
    Log("  - 结束时间:", _D(endTime));
    Log("  - 持续时间:", formatDuration(duration));
    Log("  - 本轮滚仓次数:", currentRoundRolls);
    Log("  - 累计盈利:", currentRoundTotalProfit.toFixed(2), "U");
    Log("  - 亏损金额:", currentRoundLoss.toFixed(2), "U");
    Log("  - 总体盈利:", netProfit.toFixed(2), "U");
    Log("  - 结束方式:", isStopLoss ? "❌ 止损" : "✅ 正常");
    Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    
    // 重置本轮统计数据
    currentRoundRolls = 0;
    currentRoundStartTime = 0;
    currentRoundDirection = "";
    currentRoundTotalProfit = 0;
    currentRoundLoss = 0;
    currentRoundEntryPrice = 0;
}

// 格式化时长
function formatDuration(ms) {
    var seconds = Math.floor(ms / 1000);
    var minutes = Math.floor(seconds / 60);
    var hours = Math.floor(minutes / 60);
    var days = Math.floor(hours / 24);
    
    if (days > 0) {
        return days + "天" + (hours % 24) + "时" + (minutes % 60) + "分";
    } else if (hours > 0) {
        return hours + "时" + (minutes % 60) + "分";
    } else if (minutes > 0) {
        return minutes + "分" + (seconds % 60) + "秒";
    } else {
        return seconds + "秒";
    }
}

// 重置持仓状态函数
function resetPositionState() {
    entryPrice = 0;
    lastRollPrice = 0;
    currentDirection = "";
    // 注意:不重置 rollCount、strategyCapital 和 currentRoundRolls
}

// 检查止损
function checkStopLoss(currentPrice, position) {
    var totalDrawdown = 0;
    
    if (currentDirection == "LONG") {
        totalDrawdown = (currentPrice - entryPrice) / entryPrice;
    } else {
        totalDrawdown = (entryPrice - currentPrice) / entryPrice;
    }
    
    if (totalDrawdown < -StopLossPercent) {
        Log("❌ 触发止损!回撤:", (totalDrawdown * 100).toFixed(2), "%");
        
        // 取消止盈单
        if (takeProfitOrderId) {
            Log("取消止盈单:", takeProfitOrderId);
            exchange.CancelOrder(takeProfitOrderId);
            takeProfitOrderId = null;
            Sleep(500);
        }
        
        // ========== 市价平仓(循环重试直到成功) ==========
        var profit = closePositionMarketWithRetry(currentPrice, position);
        
        // 更新策略资金池
        strategyCapital += profit;
        totalProfitRealized += profit;
        
        Log("止损亏损:", profit.toFixed(2), "U");
        Log("策略剩余资金:", strategyCapital.toFixed(2), "U");
        Log("累计盈利:", totalProfitRealized.toFixed(2), "U");
        
        // ========== 记录本轮止损亏损 ==========
        currentRoundLoss = Math.abs(profit);  // 转为正数保存
        Log("本轮止损亏损:", currentRoundLoss.toFixed(2), "U");
        
        // ========== 记录本轮滚仓结束(被止损中断) ==========
        saveRollRecord(true);
        
        // 重置状态
        resetPositionState();
        
        if (strategyCapital < 10) {
            Log("💥 策略资金不足10U,停止运行");
            throw "资金不足";
        }
        
        Log("⏳ 已止损,等待新信号...");
    }
}

// ========== 市价平仓(带重试机制,直到成功) ==========
function closePositionMarketWithRetry(currentPrice, position) {
    Log("🔴 市价平仓(循环重试模式)");
    
    var maxRetries = 10;  // 最多重试10次
    var retryCount = 0;
    
    while (retryCount < maxRetries) {
        retryCount++;
        Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
        Log("🔄 第", retryCount, "次平仓尝试");
        
        var profit = closePositionMarket(currentPrice, position);
        
        // 如果返回值不为0,说明平仓成功
        if (profit !== 0) {
            Log("✅ 平仓成功!");
            Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            return profit;
        }
        
        // 平仓失败,检查持仓是否还存在
        Sleep(2000);
        var newPosition = _C(exchange.GetPosition);
        
        if (newPosition.length == 0) {
            Log("⚠️ 持仓已不存在,可能已被其他途径平仓");
            Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
            return 0;
        }
        
        // 更新position和currentPrice
        position = newPosition[0];
        var ticker = _C(exchange.GetTicker);
        currentPrice = ticker.Last;
        
        Log("⚠️ 平仓失败,", (maxRetries - retryCount), "次重试机会剩余");
        Log("等待3秒后重试...");
        Sleep(3000);
    }
    
    // 所有重试都失败
    Log("❌ 平仓失败!已达到最大重试次数");
    Log("⚠️ 请手动检查持仓状态!");
    Log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    return 0;
}

// 市价平仓(持续检测订单状态)
function closePositionMarket(currentPrice, position) {
    Log("📤 发起市价平仓订单");

    var pos = position;
    var amount = pos.Amount;
    
    if (pos.Type == PD_LONG) {
        exchange.SetDirection("closebuy");
    } else {
        exchange.SetDirection("closesell");
    }
    
    // 市价平仓
    var orderType = pos.Type == PD_LONG ? "closebuy" : "closesell";
    var orderId = exchange.CreateOrder(Symbol, orderType, -1, amount);
    
    if (!orderId) {
        Log("❌ 平仓下单失败");
        return 0;
    }
    
    Log("平仓订单ID:", orderId, "开始持续检测...");
    
    // 持续检测订单状态
    var maxWaitTime = 30000;  // 单次等待最多30秒
    var startTime = Date.now();
    var checkCount = 0;
    
    while (Date.now() - startTime < maxWaitTime) {
        Sleep(500);
        checkCount++;
        
        var order = exchange.GetOrder(orderId);
        if (!order) {
            Log("❌ 无法获取订单信息(检测", checkCount, "次)");
            continue;
        }
        
        if (order.Status == 1) {
            // 平仓成功
            Log("✅ 订单成交,成交价:", order.AvgPrice.toFixed(pricePrecision));
            var profit = calculateProfit(pos, order.AvgPrice);
            Log("盈亏:", profit.toFixed(2), "U");
            return profit;
        } else if (order.Status == 2) {
            Log("❌ 平仓订单已被取消");
            return 0;
        }
        
        // Status == 0 表示未成交,继续等待
        if (checkCount % 10 == 0) {
            Log("⏳ 订单未成交,已检测", checkCount, "次...");
        }
    }
    
    // 超时,取消订单
    Log("⚠️ 平仓订单超时(等待30秒未成交)");
    Log("尝试取消订单:", orderId);
    
    var cancelResult = exchange.CancelOrder(orderId);
    if (cancelResult) {
        Log("✅ 订单已取消");
    } else {
        Log("⚠️ 取消订单失败,订单可能已成交或已取消");
    }
    
    Sleep(1000);
    
    // 再次检查订单状态(可能取消期间成交了)
    var finalOrder = exchange.GetOrder(orderId);
    if (finalOrder && finalOrder.Status == 1) {
        Log("✅ 订单在取消期间成交,成交价:", finalOrder.AvgPrice.toFixed(pricePrecision));
        var profit = calculateProfit(pos, finalOrder.AvgPrice);
        Log("盈亏:", profit.toFixed(2), "U");
        return profit;
    }
    
    return 0;
}

// 计算盈亏
function calculateProfit(position, closePrice) {
    var profit = 0;
    
    if (position.Type == PD_LONG) {
        // 多头盈亏 = (平仓价 - 开仓价) * 数量 * 合约面值
        profit = (closePrice - position.Price) * position.Amount * ctVal;
    } else {
        // 空头盈亏 = (开仓价 - 平仓价) * 数量 * 合约面值
        profit = (position.Price - closePrice) * position.Amount * ctVal;
    }
    
    return profit;
}

// ========== 显示状态(三个两行表格) ==========
function showStatus(account, position, price, ema5, ema10, isBullTrend, barTime) {
    
    // ========== 表格1:基本信息 ==========
    var table1 = {
        type: "table",
        title: "策略基本信息",
        cols: ["更新时间", "交易币种", "账户总资金", "策略初始资金", "策略当前资金", "策略累计盈利", "策略收益率"],
        rows: []
    };
    
    table1.rows.push([
        _D(barTime),
        Symbol,
        account.Balance.toFixed(2) + " U",
        InitialCapital + " U",
        strategyCapital.toFixed(2) + " U",
        totalProfitRealized.toFixed(2) + " U",
        ((strategyCapital / InitialCapital - 1) * 100).toFixed(2) + "%"
    ]);
    
    // ========== 表格2:持仓信息 ==========
    var table2 = {
        type: "table",
        title: "持仓信息",
        cols: [],
        rows: []
    };
    
    if (position.length > 0) {
        var pos = position[0];
        
        var totalPL = 0;
        if (pos.Type == PD_LONG) {
            totalPL = (price - entryPrice) / entryPrice;
        } else {
            totalPL = (entryPrice - price) / entryPrice;
        }
        
        var stopLossPrice = 0;
        var takeProfitPrice = 0;
        
        if (pos.Type == PD_LONG) {
            stopLossPrice = entryPrice * (1 - StopLossPercent);
            takeProfitPrice = entryPrice * 1.1;
        } else {
            stopLossPrice = entryPrice * (1 + StopLossPercent);
            takeProfitPrice = entryPrice * 0.9;
        }
        
        table2.cols = [
            "持仓状态", "持仓方向", "持仓数量", "入场价格", "持仓均价",
            "止盈价格", "止损价格", "浮动盈亏", "盈亏比例", "止盈单ID", "本轮滚仓次数"
        ];
        
        table2.rows.push([
            "✅ 持仓中",
            pos.Type == PD_LONG ? "🟢 多头" : "🔴 空头",
            pos.Amount.toFixed(amountPrecision),
            entryPrice.toFixed(pricePrecision),
            pos.Price.toFixed(pricePrecision),
            takeProfitPrice.toFixed(pricePrecision),
            stopLossPrice.toFixed(pricePrecision),
            pos.Profit.toFixed(2) + " U",
            (totalPL * 100).toFixed(2) + "%",
            takeProfitOrderId ? takeProfitOrderId : "未挂单",
            currentRoundRolls + " 次"
        ]);
        
    } else {
        table2.cols = ["持仓状态", "等待信号", "本轮滚仓次数"];
        table2.rows.push([
            "⏳ 空仓",
            isBullTrend ? "📈 等待金叉" : "📉 等待死叉",
            currentRoundRolls + " 次"
        ]);
    }
    
    // ========== 表格3:滚仓统计(近10次详细记录) ==========
    var table3 = {
        type: "table",
        title: "滚仓统计(近10次)",
        cols: ["序号", "方向", "入场价格", "开始时间", "结束时间", "滚仓次数", "盈利金额", "亏损金额", "总体盈利", "持续时间"],
        rows: []
    };
    
    if (rollHistory.length == 0) {
        table3.rows.push(["暂无", "暂无", "暂无", "暂无", "暂无", "暂无", "暂无", "暂无", "暂无", "暂无"]);
    } else {
        // 倒序显示(最新的在前)
        for (var i = rollHistory.length - 1; i >= 0; i--) {
            var record = rollHistory[i];
            var index = rollHistory.length - i;
            
            var directionText = record.direction == "LONG" ? "🟢 多头" : "🔴 空头";
            
            // 总体盈利的显示(带颜色标识)
            var netProfitText = record.netProfit.toFixed(2) + " U";
            if (record.netProfit > 0) {
                netProfitText = "+" + netProfitText;
            }
            
            // 格式化时间显示(只显示月-日 时:分)
            var startTimeStr = _D(record.startTime).substring(5, 16);  // "MM-DD HH:mm"
            var endTimeStr = _D(record.endTime).substring(5, 16);
            
            table3.rows.push([
                "#" + index,
                directionText,
                record.entryPrice ? record.entryPrice.toFixed(pricePrecision) : "未知",
                startTimeStr,
                endTimeStr,
                record.roundRolls + " 次",
                record.totalProfit.toFixed(2) + " U",
                record.loss.toFixed(2) + " U",
                netProfitText,
                formatDuration(record.duration)
            ]);
        }
    }
    
    // ========== 组合所有表格 ==========
    LogStatus(
        "`" + JSON.stringify(table1) + "`\n\n" +
        "`" + JSON.stringify(table2) + "`\n\n" +
        "`" + JSON.stringify(table3) + "`"
    );
}