avatar of ianzeng123 ianzeng123
关注 私信
2
关注
364
关注者

冒险者的游戏:滚仓策略的代码实现和应用

创建于: 2025-12-19 17:06:01, 更新于: 2025-12-29 09:04:46
comments   0
hits   243

[TOC]

冒险者的游戏:滚仓策略的代码实现和应用

引言

在量化交易领域,滚仓(Rolling Position)策略是一个颇具吸引力但也充满挑战的话题。这个策略的核心理念是:在趋势行情中,通过将已实现的盈利再次投入交易,实现复利增长。本文将深入探讨如何将滚仓这个交易想法,一步步转化为可执行的代码逻辑,重点关注思维转换的过程而非技术细节。需要特别说明的是,滚仓策略在放大收益的同时也会放大风险,本文仅供学习交流参考。


一、滚仓策略的盈利逻辑深度解析

1.1 滚仓的数学本质

滚仓示意图

滚仓策略的盈利逻辑本质上是一个复利增长模型。让我们用一个简化的例子来理解:

传统单次交易(连续3次各涨10%):

  • 初始资金:100 USDT,杠杆3倍
  • 市场涨幅:(1+10%) × (1+10%) × (1+10%) - 1 = 33.1%
  • 盈利:100 × 3 × 33.1% = 99.3 USDT
  • 最终:199.3 USDT

滚仓交易(连续3次各涨10%):

  • 第1次:100 USDT → 盈利30 USDT → 资金变130 USDT
    • 计算:100 × 3倍杠杆 × 10%涨幅 = 30
  • 第2次:130 USDT → 盈利39 USDT → 资金变169 USDT
    • 计算:130 × 3倍杠杆 × 10%涨幅 = 39
  • 第3次:169 USDT → 盈利50.7 USDT → 资金变219.7 USDT
    • 计算:169 × 3倍杠杆 × 10%涨幅 = 50.7

对比结果:

同样市场连续3次各涨10%的情况下: * 单次交易:盈利 99.3 USDT * 滚仓交易:盈利 119.7 USDT * 复利优势:20.4 USDT(提升约20.5%)

同样是连续3次各涨10%,单次交易盈利99.3 USDT,滚仓交易盈利119.7 USDT。这个差额就是复利的力量

用数学公式表达:

// 传统交易:线性增长
最终资金 = 初始资金 × (1 + 杠杆 × 涨幅)

// 滚仓交易:指数增长
最终资金 = 初始资金 × (1 + 杠杆 × 单次涨幅) ^ 滚仓次数

这揭示了滚仓的本质:将线性增长转化为指数增长。但同时也暴露了风险:一次止损可能回吐之前所有复利成果

1.2 滚仓策略的三个核心问题

在动手编写任何代码之前,我们需要从策略层面回答三个根本问题:

问题1:什么时候开始?(初次入场)
需要判断趋势的开始信号。

问题2:什么时候继续?(追加滚仓)
这是滚仓的核心:止盈后如何判断趋势是否延续?

问题3:什么时候停止?(退出观望)
- 主动退出:趋势转弱 - 被动退出:触发止损

这三个问题决定了整个策略的骨架,下面我们将逐一把它们转化为代码逻辑。


二、问题一:什么时候开始?——寻找入场的起爆点

入场信号

2.1 滚仓策略的理想与现实

让我们先理解滚仓策略最理想的应用场景。

理想场景:
想象一下,如果你能在SHIB从0.000001美元起涨时入场,或者在某个山寨币暴涨前夕建仓,通过连续滚仓,100 USDT可能变成10000 USDT甚至更多。这就是滚仓策略的终极梦想——在币种起爆前入场,抓住十倍甚至百倍的收益

残酷现实:
但问题在于,如何知道哪个币种会暴涨?什么时候会暴涨? - 如果你是项目方或内幕人士,可能提前知道利好消息 - 如果你是普通交易者,只能通过市场信号去判断

对于我们大多数人来说,想要精准捕捉这种起爆点,说白了就是撞大运。我们无法预知未来,只能通过历史数据和技术指标,尽可能提高”撞到大运”的概率。

2.2 从理想回归现实:技术指标的模拟入场

既然无法预知哪个币种会暴涨,我们能做的就是:建立一套可执行的入场规则,用技术指标模拟”趋势开始”的信号

这就像在茫茫大海中捕鱼,虽然不知道哪里有大鱼,但我们可以: - 观察水面波纹(价格波动) - 分析水流方向(趋势方向) - 选择合适的工具(技术指标)

当多个信号汇聚时,我们认为”可能”有趋势要开始了,于是入场尝试。对了,就跟着趋势滚仓赚钱;错了,及时止损退出。

2.3 入场信号的技术实现

选择技术工具:
我们使用EMA双均线系统(EMA5和EMA10)作为趋势判断工具。选择它的原因很简单: - 简单直观,易于验证 - 能快速反应价格变化 - 参数平衡了灵敏度和稳定性

核心逻辑:
通过检测均线的”金叉”(EMA5上穿EMA10)和”死叉”(EMA5下穿EMA10)来捕捉趋势转折点: - 金叉 → 做多信号 - 死叉 → 做空信号

代码思路:

// 计算EMA指标
var emaFast = TA.EMA(records, FastEMA);  // EMA5
var emaSlow = TA.EMA(records, SlowEMA);  // EMA10

// 获取当前和前一根K线的EMA值
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];

// 检测金叉:前一根K线EMA5<=EMA10,当前K线EMA5>EMA10
var bullCross = ema5_prev <= ema10_prev && ema5_current > ema10_current;

// 检测死叉:前一根K线EMA5>=EMA10,当前K线EMA5<EMA10
var bearCross = ema5_prev >= ema10_prev && ema5_current < ema10_current;

// 空仓时等待信号入场
if (bullCross) {
    Log("📈 金叉信号 - 做多");
    openPosition("LONG", currentPrice);
} else if (bearCross) {
    Log("📉 死叉信号 - 做空");
    openPosition("SHORT", currentPrice);
}

这里不深入展开金叉死叉的细节,这些都是交易中的基础概念。重点是:我们需要一个明确的、可量化的入场信号来触发滚仓的起点


三、问题二:什么时候继续?——复利滚仓的核心机制

滚仓机制

3.1 理解滚仓的本质:一个理智的冒险者游戏

滚仓策略本质上是一个理智的冒险者游戏。让我们用一个完整的场景来理解:

游戏规则:

1. 你从交易所账户中拿出100 USDT作为冒险资金
2. 这100 USDT独立管理,与账户其他资金隔离
3. 用这100 USDT开始交易:
   - 赚了 → 盈利加入资金池,继续用更大的资金交易(滚仓)
   - 亏了 → 触发止损,回到空仓状态
4. 重复这个过程,直到:
   - 要么把100 USDT亏完(游戏结束)
   - 要么滚到一个满意的金额(主动退出)

这个游戏的精妙之处在于: - 风险可控:最多亏100 USDT,不会影响账户其他资金 - 收益无限:如果趋势配合,复利可以让资金快速翻倍 - 进退有据:有明确的止盈、止损、滚仓规则

3.2 资金池的设计:实现复利的关键

这是滚仓策略中最核心的设计思想。

传统做法的问题:
假设你的交易所账户有1000 USDT: - 第1次开仓用100 USDT - 盈利30 USDT后,账户变成1030 USDT - 第2次开仓该用多少?100还是130? - 如何区分这是滚仓策略赚的还是其他操作的收益?

资金池解决方案:

// 创建一个虚拟的"策略资金池"
var strategyCapital = InitialCapital;  // 初始100 USDT

// 第1次交易
// 开仓金额 = 100 USDT
// 止盈后盈利 = 30 USDT
strategyCapital = strategyCapital + 30;  // 资金池变为130 USDT

// 第2次交易(滚仓)
var positionValue = strategyCapital * Leverage;  // 130 × 3 = 390
var amount = positionValue / price / ctVal;      // 计算开仓数量
// 自动使用了第1次的盈利,这就是复利的关键

// 止盈后盈利 = 39 USDT
strategyCapital = strategyCapital + 39;  // 资金池变为169 USDT

// 第3次交易(滚仓)
// 开仓金额 = 169 USDT(继续利滚利)

这个设计的优势: - 资金隔离: 策略只动用指定的100 USDT,不影响账户其他资金 - 自动复利: 每次盈利自动加入资金池,下次开仓自然用更大金额 - 风险可控: 最坏情况亏完100 USDT,不会超出预期 - 清晰追踪: 可以精确知道策略从100 USDT滚到了多少

3.3 滚仓决策:止盈后继续还是停止?

这是滚仓策略的灵魂环节:当止盈单成交后,我们要做一个关键决策——继续滚还是停手?

决策场景:

假设我们做多BTC:
- 入场价:45000 USDT,用100 USDT开仓
- 止盈价:49500 USDT(涨10%)
- 止盈成交,盈利30 USDT
- 现在资金池:130 USDT

问题来了:
选项A:收手,带着130 USDT退出,回到空仓
选项B:继续,用130 USDT再次开多(滚仓)

如何选择?

这个决策不能靠”感觉”,必须有明确标准。我们的判断逻辑是:趋势是否还在延续?

判断方法:
在止盈单成交的瞬间,重新计算最新的技术指标(EMA均线):

// 止盈单成交后,获取最新K线数据
var records = _C(exchange.GetRecords, PERIOD_M1);
var emaFast = TA.EMA(records, FastEMA);
var emaSlow = TA.EMA(records, SlowEMA);

var ema5_current = emaFast[emaFast.length - 1];
var ema10_current = emaSlow[emaSlow.length - 1];

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("⏸️ 决策:不滚仓,等待新信号");
    }
}

3.4 滚仓执行流程

如果决策是”继续滚仓”:

if (shouldRoll) {
    // 1. 增加滚仓计数
    currentRoundRolls++;
    
    Log("🔄 执行滚仓操作... (本轮第", currentRoundRolls, "次滚仓)");
    
    // 2. 获取最新价格
    var ticker = _C(exchange.GetTicker);
    var newPrice = ticker.Last;
    
    // 3. 基于新资金池重新开仓
    if (openPosition(currentDirection, newPrice)) {
        Log("✅ 滚仓成功!");
        // 4. 挂新的止盈单(在openPosition函数中完成)
        // 5. 设置新的止损价(在checkStopLoss函数中监控)
    } else {
        Log("❌ 滚仓失败,等待新信号");
        saveRollRecord(false);
        resetPositionState();
    }
}

如果决策是”停止”:

else {
    // 1. 保存本轮统计
    saveRollRecord(false);  // false表示正常结束,非止损
    
    // 2. 保留资金池金额
    // strategyCapital 保持当前值,等待下次机会
    
    // 3. 回到空仓状态
    resetPositionState();
    
    Log("⏳ 已平仓,等待新信号...");
}

这个流程的关键点: - 每次止盈后立即判断,不拖延 - 判断标准客观(均线关系),不主观臆测 - 继续就加大仓位,停止就保留成果

3.5 复利的威力与代价

让我们通过一个完整案例感受复利的威力:

成功案例:

初始资金:100 USDT
止盈比例:10%
杠杆:3倍

第1次:100 USDT → 盈利30 → 资金池130
第2次:130 USDT → 盈利39 → 资金池169
第3次:169 USDT → 盈利50.7 → 资金池219.7
第4次:219.7 USDT → 盈利65.9 → 资金池285.6
第5次:285.6 USDT → 盈利85.7 → 资金池371.3

连续滚5次,100变成371.3,增长271%!

失败案例:

第1次:100 USDT → 盈利30 → 资金池130
第2次:130 USDT → 盈利39 → 资金池169
第3次:169 USDT → 趋势反转 → 触发止损
止损比例5%,亏损:169 × 3 × 5% = 25.35 USDT
剩余资金:169 - 25.35 = 143.65 USDT

原本从100滚到169,一次止损后只剩143.65

这就是滚仓的双刃剑: - 成功时: 指数级增长,令人振奋 - 失败时: 快速回吐,甚至亏损


四、问题三:什么时候停止?——止损是最后的防线

止损机制

4.1 两种退出方式

主动退出:趋势转弱
这种情况在”问题二”中已经处理了——止盈后判断趋势不支持继续,主动选择停止。这是理想的退出方式,带着盈利离场。

被动退出:触发止损
这是我们现在要重点讨论的——当行情走反,价格触及止损线,被迫平仓。

4.2 止损的必要性

很多人不喜欢止损,因为: - 止损意味着认错 - 止损会产生实际亏损 - 有时候止损后价格又涨回来了

但在滚仓策略中,止损是保命的底线。想想看:

如果没有止损:
第1次:100 → 滚到 169
第2次:169 → 趋势反转,不止损
价格持续下跌:169 → 150 → 120 → 80 → 50...
最终可能全亏,甚至爆仓
如果有止损:
第1次:100 → 滚到 169
第2次:169 → 趋势反转,触发止损
止损5%:亏损 25.35
剩余:143.65
虽然亏了,但保留了大部分资金
可以等待下一个机会

止损的本质: 用小的确定性亏损,避免大的不确定性风险。

4.3 止损的代码实现

// 检查止损
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), "%");
        
        // 1. 取消止盈单
        if (takeProfitOrderId) {
            Log("取消止盈单:", takeProfitOrderId);
            exchange.CancelOrder(takeProfitOrderId);
            takeProfitOrderId = null;
            Sleep(500);
        }
        
        // 2. 市价平仓(循环重试直到成功)
        var profit = closePositionMarketWithRetry(currentPrice, position);
        
        // 3. 更新策略资金池
        strategyCapital += profit;  // profit是负数
        totalProfitRealized += profit;
        
        Log("止损亏损:", profit.toFixed(2), "U");
        Log("策略剩余资金:", strategyCapital.toFixed(2), "U");
        
        // 4. 记录本轮止损亏损
        currentRoundLoss = Math.abs(profit);
        Log("本轮止损亏损:", currentRoundLoss.toFixed(2), "U");
        
        // 5. 保存本轮滚仓记录(被止损中断)
        saveRollRecord(true);  // true表示止损结束
        
        // 6. 重置状态
        resetPositionState();
        
        // 7. 检查资金是否充足
        if (strategyCapital < 10) {
            Log("💥 策略资金不足10U,停止运行");
            throw "资金不足";
        }
        
        Log("⏳ 已止损,等待新信号...");
    }
}

4.4 游戏结束的条件

还记得我们说的”理智的冒险者游戏”吗?这个游戏有明确的结束条件:

条件1:资金池归零

if (strategyCapital <= 0) {
    Log("💥 游戏结束:资金池已归零");
    Log("本次冒险失败,100 USDT全部亏光");
    throw "资金耗尽";
}

条件2:主动退出

if (strategyCapital >= 目标金额) {
    Log("🎉 达到目标金额,可以选择主动退出");
    Log("锁定利润,开始新一轮100 USDT的游戏");
}

条件3:达到最大滚仓次数

if (连续滚仓次数 >= 10次) {
    Log("⚠️ 达到最大滚仓次数,主动退出");
    Log("持续时间太长,风险累积,见好就收");
    saveRollRecord(false);
    resetPositionState();
}

4.5 风险与收益的平衡

整个滚仓策略的设计,核心就是在风险与收益之间找平衡

收益端: - 复利增长:每次止盈后资金池变大 - 趋势捕捉:在上涨/下跌趋势中持续获利 - 无上限:理论上可以无限滚下去

风险端: - 止损保护:单次最多亏损资金池的5% - 资金隔离:最多亏100 USDT - 趋势判断:避免在震荡市中频繁止损


五、实战回测:TRUMP_USDT案例分析

TRUMP_USDT 币安期货上市首日(2025年1月20日至2025年1月21日)回测参考分析:

回测权益曲线

滚仓统计表格

从回测结果可以看到:

亮点表现: - 策略成功捕捉到了TRUMP上市初期的剧烈波动 - 通过多次滚仓,实现了资金的快速增长 - 止盈机制有效锁定了趋势中的利润

风险暴露: - 在趋势反转时,止损导致部分利润回吐 - 震荡行情中出现了假突破信号 - 单一币种的集中风险较高

关键数据: - 总滚仓次数:X次 - 最大单轮滚仓:X次 - 最大回撤:X% - 最终收益率:X%


六、策略的本质与局限

6.1 这个策略在模拟什么?

通过上述分析,我们可以清楚地看到,这个策略本质上是在模拟:

一个理智的冒险者的交易行为: - 有明确的入场规则(不是冲动交易) - 有止盈目标(不贪婪) - 有止损纪律(不死扛) - 有滚仓判断(会利用盈利) - 有资金限制(控制风险)

它的核心逻辑是: 1. 拿出固定资金(100 USDT)进行尝试 2. 遇到趋势,跟随趋势赚钱 3. 赚到钱后,用盈利继续交易(利滚利) 4. 如果趋势转弱,及时停止 5. 如果判断错误,快速止损 6. 直到资金亏完或滚到满意金额

6.2 策略的局限性

局限1:依赖趋势市场
这个策略在震荡市中表现很差,因为: - 频繁出现假突破 - 止盈后又回调,无法滚仓 - 反复止损,消耗资金池

局限2:参数敏感
止盈10%、止损5%这些参数并非最优: - 不同币种波动率不同 - 不同市况需要不同参数 - 固定参数难以适应所有情况

局限3:无法预测起爆点
正如前面所说,我们用技术指标入场,本质上是”撞大运”: - 可能错过真正的大行情 - 可能在假突破时入场 - 无法像内幕人士那样提前布局

6.3 改进方向

方向1:结合工作流筛选币种
- 不是随便找个币种就滚 - 而是先用工作流筛选出热门、爆发力强的币种 - 比如:社交媒体讨论度激增、交易量异常、链上数据活跃等 - 在这些币种上使用滚仓策略,成功率会更高

方向2:动态调整参数
- 根据币种的历史波动率调整止盈止损比例 - 波动大的币种,适当放大止损空间 - 波动小的币种,可以降低止盈目标

方向3:多资金池并行
- 不是把100 USDT放在一个币种 - 而是分成5个20 USDT,在5个潜力币种上同时滚 - 分散风险,提高”撞到大运”的概率


结语

通过三个核心问题的推演,我们完整地展示了如何将滚仓这个交易想法转化为代码逻辑。这个过程的本质是:把一个理智的冒险者的交易思维,用精确的规则和数据结构表达出来。

重要提示:

这只是一个滚仓思路的模拟实现,实际上滚仓是一个非常需要市场经验的交易策略。本策略只是一个工具,后期可以结合工作流判断热门或者极具爆发力的币种,使用该工具,会为我们带来更多惊喜。

记住: - 最多亏100 USDT,风险可控 - 如果运气好遇到大趋势,可能翻几倍甚至几十倍 - 但更多时候,可能是小赚小亏,反复试探 - 这是一场需要耐心和纪律的游戏

没有稳赚不赔的策略,滚仓只是一个工具。真正决定成败的,是你能否: - 找到有潜力的币种(结合工作流筛选) - 坚持执行止损(不要死扛) - 在大趋势来临时敢于滚仓(不要过早退出) - 保持理智(不被情绪左右)

祝各位在量化交易的道路上,找到属于自己的”大运”!

完整策略地址:**策略源码 -> ** https://www.fmz.com/strategy/521864

完整策略代码:

/*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) + "`"
    );
}
相关推荐