黄金代币统计套利策略框架


创建日期: 2026-02-05 13:26:08 最后修改: 2026-03-09 11:11:18
复制: 13 点击次数: 177
avatar of ianzeng123 ianzeng123
2
关注
413
关注者

策略概述

本策略针对加密货币市场中两种黄金挂钩代币(PAXG 与 XAUT)之间长期存在的价格联动关系,运用统计套利原理,捕捉两者价差偏离均值时的回归机会。策略以秒级频率轮询行情,以分钟级K线作为统计样本,属于低频、中性的市场中性策略。


核心逻辑

价差建模

策略持续采集两个合约的分钟K线收盘价,计算实时价差序列(以百分比表示):

价差 = (价格₁ - 价格₂) / 价格₂ × 100%

在滚动统计窗口内,计算价差的均值(μ)与标准差(σ),构建动态统计区间: - 上轨 = μ + N×σ(默认 N=2) - 下轨 = μ - N×σ

开仓信号

条件 操作
当前价差 > 上轨 做空第一腿 / 做多第二腿
当前价差 < 下轨 做多第一腿 / 做空第二腿

开仓时记录当前均值作为目标回归价差

平仓信号

价差回归至开仓时的均值水平即触发平仓,实现价差收敛利润的兑现。


执行机制

顺序双腿下单

为降低单腿暴露风险,策略采用严格的顺序执行逻辑: 1. 先对第一个合约下市价单,等待成交确认 2. 成交后,再对第二个合约下市价单 3. 若第二腿下单失败,自动平掉第一腿,避免裸露敞口

订单超时保护

每笔订单设有超时机制,超时后自动撤销,防止挂单长期未成交导致的仓位错乱。


风险控制

  • 最小波动率过滤:若当前统计窗口内价差标准差过小(市场极度平静),则不开仓,避免在无效区间内频繁进出
  • 样本量校验:统计窗口样本不足时暂停交易,保证统计指标的可靠性
  • 手动干预命令:支持运行时发送”强制平仓”、”清除数据”、”刷新权益”等指令,便于人工接管异常情况

监控与展示

策略实时输出以下面板信息: - 两个合约的当前价格与更新时间 - 价差统计指标(均值、标准差、上下轨、Z-Score) - 当前持仓状态(方向、开仓价差、目标价差、浮动盈亏、持仓时长) - 历史交易记录与胜率统计 - 账户权益变化与累计盈亏曲线 - 价差历史分布(最小值、中位数、最大值、波动范围)

视频链接:黄金代币统计套利实战:捕捉 PAXG&XAU 价差机会

策略源码
/*backtest
start: 2026-01-28 00:00:00
end: 2026-02-01 16:08:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_Bybit","currency":"XAUT_USDT","balance":500000}]
*/

/*
黄金代币统计套利策略 
===========================
- 统计套利:均值±2σ开仓,回归均值平仓
- 低频策略:使用分钟K线数据
- 顺序下单:先开第一腿,成功后开第二腿
- 市价单:使用-1市价单
*/

// ==================== 参数 ====================
var Symbol1 = "PAXG_USDT.swap";      // 第一个交易对
var Symbol2 = "XAUT_USDT.swap";       // 第二个交易对
var StatPeriod = 60;                 // 统计周期(分钟K线数量)
var StdMultiple = 2;                 // 标准差倍数
var TradeAmount = 1;                // 每次交易数量
var MinStd = 0.05;                   // 最小标准差(%)
var CheckInterval = 10000;           // 检查间隔(毫秒)
var OrderTimeout = 30000;            // 订单超时(毫秒)

// ==================== 工具函数 ====================
var $ = {
    now: Date.now,
    
    n: function(v, p) { 
        return v == null || isNaN(v) ? '-' : parseFloat(v).toFixed(p || 4); 
    },
    
    time: function(ts) {
        var d = new Date(ts);
        return ('0'+d.getHours()).slice(-2) + ':' + ('0'+d.getMinutes()).slice(-2) + ':' + ('0'+d.getSeconds()).slice(-2);
    },
    
    duration: function(ms) {
        var s = ms/1000|0, m = s/60|0, h = m/60|0;
        return h > 0 ? h+'时'+(m%60)+'分' : m > 0 ? m+'分'+(s%60)+'秒' : s+'秒';
    },
    
    color: function(v, p) {
        var s = $.n(v, p);
        return v > 0 ? '🟢+'+s : v < 0 ? '🔴'+s : s;
    }
};

// ==================== 全局状态 ====================
var S = {
    start: 0,
    lastSave: 0,
    lastCheck: 0,
    
    // K线数据缓存
    records1: [],
    records2: [],
    
    // 统计数据
    stat: { 
        mean: 0, 
        std: 0, 
        upper: 0, 
        lower: 0, 
        count: 0,
        spreads: []
    },
    
    // 当前价格
    price1: 0,
    price2: 0,
    currentSpread: 0,
    
    // 持仓状态
    position: {
        active: false,
        direction: 0,        // 1: 空Symbol1/多Symbol2, -1: 多Symbol1/空Symbol2
        openSpread: 0,
        targetSpread: 0,     // 目标回归价差(开仓时的均值)
        openTime: 0,
        openEquity: 0,
        amount: 0
    },
    
    // 交易统计
    trades: [],
    totalPnL: 0,
    winCount: 0,
    lossCount: 0,
    
    // 账户
    equity: {
        current: 0,
        initial: 0
    }
};

// ==================== K线和价差计算 ====================
var dataManager = {
    // 获取K线数据
    fetchRecords: function() {

        var records1 = exchange.GetRecords(Symbol1,PERIOD_M1);
        
        var records2 = exchange.GetRecords(Symbol2,PERIOD_M1);
        
        if (!records1 || !records2 || records1.length < StatPeriod || records2.length < StatPeriod) {
            return false;
        }
        
        S.records1 = records1.slice(-StatPeriod);
        S.records2 = records2.slice(-StatPeriod);
        
        // 更新当前价格
        S.price1 = records1[records1.length - 1].Close;
        S.price2 = records2[records2.length - 1].Close;
        S.currentSpread = (S.price1 - S.price2) / S.price2 * 100;
        
        return true;
        
    },
    
    // 计算价差序列
    calcSpreadSeries: function() {
        var spreads = [];
        var len = Math.min(S.records1.length, S.records2.length);
        
        for (var i = 0; i < len; i++) {
            var p1 = S.records1[i].Close;
            var p2 = S.records2[i].Close;
            if (p2 > 0) {
                spreads.push((p1 - p2) / p2 * 100);
            }
        }
        
        return spreads;
    },
    
    // 计算统计指标
    calcStatistics: function() {
        var spreads = this.calcSpreadSeries();
        if (spreads.length < StatPeriod) {
            return false;
        }
        
        // 计算均值
        var sum = 0;
        for (var i = 0; i < spreads.length; i++) {
            sum += spreads[i];
        }
        var mean = sum / spreads.length;
        
        // 计算标准差
        var sqSum = 0;
        for (var i = 0; i < spreads.length; i++) {
            sqSum += Math.pow(spreads[i] - mean, 2);
        }
        var std = Math.sqrt(sqSum / spreads.length);
        
        S.stat.mean = mean;
        S.stat.std = std;
        S.stat.upper = mean + StdMultiple * std;
        S.stat.lower = mean - StdMultiple * std;
        S.stat.count = spreads.length;
        S.stat.spreads = spreads;
        
        return true;
    }
};

// ==================== 订单管理 ====================
var orderManager = {
    // 等待订单成交
    waitOrderFilled: function(orderId, timeout) {
        var startTime = $.now();
        
        while ($.now() - startTime < timeout) {
            try {
                var order = exchange.GetOrder(orderId);
                if (!order) {
                    Sleep(500);
                    continue;
                }
                
                // 状态: 0=未成交, 1=已成交, 2=已撤销
                if (order.Status === 1) {
                    return { success: true, order: order };
                }
                
                if (order.Status === 2) {
                    return { success: false, reason: '订单已撤销' };
                }
                
                Sleep(500);
            } catch(e) {
                Sleep(500);
            }
        }
        
        // 超时,尝试撤单
        try {
            exchange.CancelOrder(orderId);
        } catch(e) {}
        
        return { success: false, reason: '订单超时' };
    },
    
    // 下单并等待成交(市价单)
    placeAndWait: function(symbol, side, amount) {
        try {            
            var orderId = exchange.CreateOrder(symbol, side, -1, amount);
            if (!orderId) {
                return { success: false, reason: '下单失败' };
            }
            
            Log("📤 下单: " + symbol + " " + side + " " + amount + " | 订单ID: " + orderId);
            
            return this.waitOrderFilled(orderId, OrderTimeout);
        } catch(e) {
            return { success: false, reason: e.message };
        }
    },
    
    // 顺序开双腿
    openPair: function(direction) {
        var side1, side2;
        
        if (direction === 1) {
            // 空Symbol1,多Symbol2
            side1 = "sell";
            side2 = "buy";
        } else {
            // 多Symbol1,空Symbol2
            side1 = "buy";
            side2 = "sell";
        }
        
        Log("════════════════════════════════════");
        Log("📊 开仓 | " + (direction === 1 ? "空"+Symbol1+"/多"+Symbol2 : "多"+Symbol1+"/空"+Symbol2));
        
        // 第一腿
        var result1 = this.placeAndWait(Symbol1, side1, TradeAmount);
        if (!result1.success) {
            Log("❌ 第一腿开仓失败: " + result1.reason);
            Log("════════════════════════════════════");
            return false;
        }
        Log("✅ 第一腿成交");
        
        // 第二腿
        var result2 = this.placeAndWait(Symbol2, side2, TradeAmount);
        if (!result2.success) {
            Log("⚠️ 第二腿开仓失败,平掉第一腿");
            var closeSide1 = direction === 1 ? "closesell" : "closebuy";
            this.placeAndWait(Symbol1, closeSide1, TradeAmount);
            Log("════════════════════════════════════");
            return false;
        }
        Log("✅ 第二腿成交");
        
        Log("🎉 双腿开仓成功!");
        Log("════════════════════════════════════");
        return true;
    },
    
    // 顺序平双腿
    closePair: function(direction) {
        var closeSide1, closeSide2;
        
        if (direction === 1) {
            closeSide1 = "closesell";
            closeSide2 = "closebuy";
        } else {
            closeSide1 = "closebuy";
            closeSide2 = "closesell";
        }
        
        Log("════════════════════════════════════");
        Log("📊 平仓中...");
        
        // 平第一腿
        var result1 = this.placeAndWait(Symbol1, closeSide1, S.position.amount);
        if (!result1.success) {
            Log("⚠️ 第一腿平仓失败: " + result1.reason + ",继续尝试第二腿");
        } else {
            Log("✅ 第一腿平仓成功");
        }
        
        // 平第二腿
        var result2 = this.placeAndWait(Symbol2, closeSide2, S.position.amount);
        if (!result2.success) {
            Log("⚠️ 第二腿平仓失败: " + result2.reason);
        } else {
            Log("✅ 第二腿平仓成功");
        }
        
        Log("════════════════════════════════════");
        return result1.success && result2.success;
    }
};

// ==================== 套利核心逻辑 ====================
var arbCore = {
    // 检查开仓条件
    shouldOpen: function() {
        if (S.stat.count < StatPeriod) {
            return { open: false, reason: '样本不足(' + S.stat.count + '/' + StatPeriod + ')' };
        }
        
        if (S.stat.std < MinStd) {
            return { open: false, reason: '波动太小(' + $.n(S.stat.std, 4) + '%)' };
        }
        
        if (S.currentSpread > S.stat.upper) {
            return { open: true, direction: 1, reason: '超上轨' };
        }
        
        if (S.currentSpread < S.stat.lower) {
            return { open: true, direction: -1, reason: '超下轨' };
        }
        
        return { open: false, reason: '区间内' };
    },
    
    // 检查平仓条件
    shouldClose: function() {
        if (!S.position.active) {
            return { close: false };
        }
        
        var target = S.position.targetSpread;
        
        if (S.position.direction === 1 && S.currentSpread <= target) {
            return { close: true, reason: '回归均值(目标:' + $.n(target, 4) + '%)' };
        }
        
        if (S.position.direction === -1 && S.currentSpread >= target) {
            return { close: true, reason: '回归均值(目标:' + $.n(target, 4) + '%)' };
        }
        
        return { close: false };
    },
    
    // Z-Score计算
    zScore: function() {
        if (S.stat.std <= 0) return 0;
        return (S.currentSpread - S.stat.mean) / S.stat.std;
    },
    
    // 获取当前权益
    getEquity: function() {
        try {
            var acc = exchange.GetAccount();
            if (acc) {
                S.equity.current = acc.Equity || acc.Balance || 0;
                return S.equity.current;
            }
        } catch(e) {}
        return S.equity.current;
    },
    
    // 开仓
    open: function(direction) {
        if (S.position.active) return false;
        
        var equityBefore = this.getEquity();
        
        Log("价差:" + $.n(S.currentSpread, 4) + "% μ:" + $.n(S.stat.mean, 4) + "% σ:" + $.n(S.stat.std, 4) + "%");
        
        var success = orderManager.openPair(direction);
        
        if (success) {
            S.position = {
                active: true,
                direction: direction,
                openSpread: S.currentSpread,
                targetSpread: S.stat.mean,  // 记录开仓时的均值作为目标
                openTime: $.now(),
                openEquity: equityBefore,
                amount: TradeAmount
            };
            Log("目标回归价差: " + $.n(S.stat.mean, 4) + "%");
            return true;
        }
        
        return false;
    },
    
    // 平仓
    close: function(reason) {
        if (!S.position.active) return false;
        
        var pos = S.position;
        
        Log("开仓:" + $.n(pos.openSpread, 4) + "% 当前:" + $.n(S.currentSpread, 4) + "% 目标:" + $.n(pos.targetSpread, 4) + "%");
        
        orderManager.closePair(pos.direction);
        
        // 计算盈亏
        var equityAfter = this.getEquity();
        var pnlUsd = equityAfter - pos.openEquity;
        
        S.totalPnL += pnlUsd;
        if (pnlUsd > 0) S.winCount++;
        else S.lossCount++;
        
        // 记录交易
        S.trades.push({
            t: $.now(),
            direction: pos.direction,
            openSpread: pos.openSpread,
            closeSpread: S.currentSpread,
            targetSpread: pos.targetSpread,
            pnlUsd: pnlUsd,
            reason: reason,
            holdTime: $.now() - pos.openTime
        });
        
        if (S.trades.length > 100) {
            S.trades = S.trades.slice(-50);
        }
        
        Log("✅ 平仓完成");
        Log("盈亏: " + $.color(pnlUsd, 4) + " USD");
        Log("累计: " + $.color(S.totalPnL, 4) + " USD");
        
        // 重置持仓
        S.position = {
            active: false,
            direction: 0,
            openSpread: 0,
            targetSpread: 0,
            openTime: 0,
            openEquity: 0,
            amount: 0
        };
        
        return true;
    },
    
    // 主逻辑
    tick: function() {
        // 获取数据
        if (!dataManager.fetchRecords()) {
            return;
        }
        
        // 计算统计
        if (!dataManager.calcStatistics()) {
            return;
        }
        
        // 检查平仓
        if (S.position.active) {
            var closeCheck = this.shouldClose();
            if (closeCheck.close) {
                this.close(closeCheck.reason);
                return;
            }
        }
        
        // 检查开仓
        if (!S.position.active) {
            var openCheck = this.shouldOpen();
            if (openCheck.open) {
                this.open(openCheck.direction);
            }
        }
    }
};

// ==================== 存储 ====================
var store = {
    save: function() {
        _G('position', S.position);
        _G('trades', S.trades);
        _G('totalPnL', S.totalPnL);
        _G('winCount', S.winCount);
        _G('lossCount', S.lossCount);
        _G('equityInitial', S.equity.initial);
    },
    
    load: function() {
        var v;
        if ((v = _G('position'))) S.position = v;
        if ((v = _G('trades'))) S.trades = v;
        if ((v = _G('totalPnL')) != null) S.totalPnL = v;
        if ((v = _G('winCount')) != null) S.winCount = v;
        if ((v = _G('lossCount')) != null) S.lossCount = v;
        if ((v = _G('equityInitial')) != null) S.equity.initial = v;
    }
};

// ==================== 显示 ====================
function render() {
    var now = $.now();
    var tables = [];
    
    // 行情表
    var t1 = { 
        type: 'table', 
        title: '💹 行情', 
        cols: ['币种', '价格', '更新时间'], 
        rows: [] 
    };
    t1.rows.push([Symbol1, $.n(S.price1, 2), S.records1.length > 0 ? $.time(S.records1[S.records1.length-1].Time) : '-']);
    t1.rows.push([Symbol2, $.n(S.price2, 2), S.records2.length > 0 ? $.time(S.records2[S.records2.length-1].Time) : '-']);
    tables.push(t1);
    
    // 统计表
    var t2 = { 
        type: 'table', 
        title: '📊 统计套利 (' + StatPeriod + '分钟/' + StdMultiple + 'σ)', 
        cols: ['指标', '值', '指标', '值'], 
        rows: [] 
    };
    
    var z = arbCore.zScore();
    var zIcon = Math.abs(z) >= StdMultiple ? '🔴' : Math.abs(z) >= 1 ? '🟡' : '🟢';
    
    t2.rows.push(['当前价差', $.n(S.currentSpread, 4) + '%', 'Z-Score', zIcon + $.n(z, 2) + 'σ']);
    t2.rows.push(['均值μ', $.n(S.stat.mean, 4) + '%', '标准差σ', $.n(S.stat.std, 4) + '%']);
    t2.rows.push(['上轨+' + StdMultiple + 'σ', $.n(S.stat.upper, 4) + '%', '下轨-' + StdMultiple + 'σ', $.n(S.stat.lower, 4) + '%']);
    
    var openCheck = arbCore.shouldOpen();
    t2.rows.push(['样本数', S.stat.count + '/' + StatPeriod, '信号', openCheck.open ? '🟢' + openCheck.reason : '⏸️' + openCheck.reason]);
    tables.push(t2);
    
    // 持仓表
    var t3 = { 
        type: 'table', 
        title: '📈 策略持仓', 
        cols: ['指标', '值', '指标', '值'], 
        rows: [] 
    };
    
    if (S.position.active) {
        var dir = S.position.direction === 1 ? '空' + Symbol1 + '/多' + Symbol2 : '多' + Symbol1 + '/空' + Symbol2;
        var unrealizedPnL = S.equity.current - S.position.openEquity;
        var spreadChange = S.currentSpread - S.position.openSpread;
        var toTarget = S.position.direction === 1 ? 
            S.currentSpread - S.position.targetSpread : 
            S.position.targetSpread - S.currentSpread;
        
        t3.rows.push(['状态', '🔴持仓', '方向', dir]);
        t3.rows.push(['开仓价差', $.n(S.position.openSpread, 4) + '%', '当前价差', $.n(S.currentSpread, 4) + '%']);
        t3.rows.push(['目标价差', $.n(S.position.targetSpread, 4) + '%', '距目标', $.n(toTarget, 4) + '%']);
        t3.rows.push(['价差变化', $.color(spreadChange, 4) + '%', '数量', S.position.amount]);
        t3.rows.push(['浮动盈亏', $.color(unrealizedPnL, 4) + ' USD', '持仓时间', $.duration(now - S.position.openTime)]);
    } else {
        t3.rows.push(['状态', '🟢空仓', '-', '-']);
    }
    tables.push(t3);
    
    // 交易统计表
    var t4 = { 
        type: 'table', 
        title: '📊 交易统计', 
        cols: ['指标', '值', '指标', '值'], 
        rows: [] 
    };
    
    var wr = (S.winCount + S.lossCount) > 0 ? (S.winCount / (S.winCount + S.lossCount) * 100).toFixed(1) : '-';
    t4.rows.push(['累计盈亏', $.color(S.totalPnL, 4) + ' USD', '胜率', wr + '%']);
    t4.rows.push(['胜/负', S.winCount + '/' + S.lossCount, '交易数', S.trades.length]);
    t4.rows.push(['运行时间', $.duration(now - S.start), '交易量', TradeAmount]);
    tables.push(t4);
    
    // 权益表
    var t5 = { 
        type: 'table', 
        title: '💰 账户权益', 
        cols: ['指标', '值', '指标', '值'], 
        rows: [] 
    };
    
    var totalChange = S.equity.current - S.equity.initial;
    t5.rows.push(['当前权益', $.n(S.equity.current, 4), '初始权益', $.n(S.equity.initial, 4)]);
    t5.rows.push(['总变化', $.color(totalChange, 4), '-', '-']);
    tables.push(t5);
    
    // 最近交易表
    if (S.trades.length > 0) {
        var t6 = { 
            type: 'table', 
            title: '📝 最近交易', 
            cols: ['时间', '方向', '开仓', '平仓', '目标', '盈亏', '原因'], 
            rows: [] 
        };
        
        S.trades.slice(-5).reverse().forEach(function(tr) {
            var dir = tr.direction === 1 ? '空P多X' : '多P空X';
            t6.rows.push([
                $.time(tr.t), 
                dir, 
                $.n(tr.openSpread, 4) + '%', 
                $.n(tr.closeSpread, 4) + '%',
                $.n(tr.targetSpread, 4) + '%',
                $.color(tr.pnlUsd, 4), 
                tr.reason
            ]);
        });
        tables.push(t6);
    }
    
    // 价差分布表
    if (S.stat.spreads.length > 0) {
        var t7 = { 
            type: 'table', 
            title: '📈 价差分布', 
            cols: ['指标', '值'], 
            rows: [] 
        };
        
        var spreads = S.stat.spreads.slice();
        spreads.sort(function(a, b) { return a - b; });
        
        var min = spreads[0];
        var max = spreads[spreads.length - 1];
        var median = spreads[Math.floor(spreads.length / 2)];
        
        t7.rows.push(['最小值', $.n(min, 4) + '%']);
        t7.rows.push(['中位数', $.n(median, 4) + '%']);
        t7.rows.push(['最大值', $.n(max, 4) + '%']);
        t7.rows.push(['波动范围', $.n(max - min, 4) + '%']);
        tables.push(t7);
    }
    
    // 更新收益曲线
    LogProfit(S.totalPnL, "&");
    
    // 状态栏
    var posIcon = S.position.active ? '📈' : '⏸️';
    var z = arbCore.zScore();
    var sigIcon = Math.abs(z) >= StdMultiple ? '🔴' : Math.abs(z) >= 1 ? '🟡' : '🟢';
    
    LogStatus(
        '🟢' + posIcon + sigIcon + ' | ' + _D() + 
        ' | 价差:' + $.n(S.currentSpread, 3) + '%' +
        ' | μ:' + $.n(S.stat.mean, 3) + '% σ:' + $.n(S.stat.std, 3) + '%' +
        ' | 盈亏:' + $.color(S.totalPnL, 2) + '\n' +
        '命令: 清除|强制平仓|刷新权益\n' +
        tables.map(function(t) { return '`' + JSON.stringify(t) + '`'; }).join('\n')
    );
}

// ==================== 命令处理 ====================
function handleCommand() {
    var c = GetCommand();
    if (!c) return;
    
    if (c === '清除') {
        _G(null);
        S.stat = { mean: 0, std: 0, upper: 0, lower: 0, count: 0, spreads: [] };
        S.position = { active: false, direction: 0, openSpread: 0, targetSpread: 0, openTime: 0, openEquity: 0, amount: 0 };
        S.trades = [];
        S.totalPnL = 0;
        S.winCount = 0;
        S.lossCount = 0;
        S.equity.initial = 0;
        Log("✅ 已清除所有数据");
    }
    else if (c === '强制平仓') {
        if (S.position.active) {
            arbCore.close('手动平仓');
        } else {
            Log("⚠️ 当前无持仓");
        }
    }
    else if (c === '刷新权益') {
        arbCore.getEquity();
        Log("✅ 权益已刷新: " + $.n(S.equity.current, 4));
    }
}

// ==================== 主函数 ====================
function main() {
    LogReset(0);
    SetErrorFilter("order not found|GetOrder");
    
    Log("════════════════════════════════════");
    Log("🚀 Lighter 统计套利 - 低频版本 V1.0");
    Log("════════════════════════════════════");
    Log("交易对: " + Symbol1 + " / " + Symbol2);
    Log("统计周期: " + StatPeriod + "分钟 | 阈值: " + StdMultiple + "σ");
    Log("交易量: " + TradeAmount + " | 最小σ: " + MinStd + "%");
    Log("检查间隔: " + (CheckInterval / 1000) + "秒");
    Log("════════════════════════════════════");
    
    S.start = $.now();
    store.load();
    
    // 初始化权益
    S.equity.current = arbCore.getEquity();
    if (S.equity.initial === 0) {
        S.equity.initial = S.equity.current;
    }
    Log("初始权益: " + $.n(S.equity.initial, 4));
    
    if (S.position.active) {
        Log("📈 恢复持仓: " + (S.position.direction === 1 ? "空" + Symbol1 + "/多" + Symbol2 : "多" + Symbol1 + "/空" + Symbol2));
        Log("目标回归价差: " + $.n(S.position.targetSpread, 4) + "%");
    }
    
    Log("════════════════════════════════════");
    
    while (true) {
        try {
            var now = $.now();
            
            // 按间隔检查
            if (now - S.lastCheck >= CheckInterval) {
                S.lastCheck = now;
                
                // 刷新权益
                arbCore.getEquity();
                
                // 执行套利逻辑
                arbCore.tick();
            }
            
            // 处理命令
            handleCommand();
            
            // 更新显示
            render();
            
            // 定期保存
            if (now - S.lastSave > 60000) {
                S.lastSave = now;
                store.save();
            }
            
        } catch(e) {
            Log("❌ 错误: " + e.message);
        }
        
        Sleep(1000);
    }
}

function onexit() {
    store.save();
    Log("👋 策略退出,数据已保存");
}