策略源码
// 仅做学术研究使用,请勿直接用于实盘
// ==================== 全局配置 ====================
const CONFIG = {
// === 平稳性检测参数 ===
adfWindow: 1000,
adfPValueThreshold: 0.1,
stationarityCheckInterval: 3600000,
consecutiveFailThreshold: 3,
// === 入场/出场信号参数 ===
zScoreEntry: 1.0,
zScoreExitTarget: 0.5,
zScoreMaxDeviation: 0.5,
// === 风控参数 ===
stopLossRate: 0.015, // 只保留止损,去掉止盈
maxHoldDays: 10,
// === 限价单参数 ===
limitOrderSpreadRatio: 0.3,
orderWaitTimeout: 45000,
orderCheckInterval: 1000,
// === 冷却期参数 ===
cooldownDuration: 600000, // 10分钟
opportunityLostCooldown: 60000, // 1分钟
// === 平仓验证参数 ===
spreadTolerance: 30,
slippageWarning: 50,
closeOrderTimeout: 15000,
// === 目标区间动态调整参数 ===
minProfitSpaceRate: 0.005,
minProfitSpaceUSDT: 50,
useFixedProfitSpace: false,
// === 余额检查参数 ===
minUSDTReserve: 10, // 最低保留USDT
balanceCheckRetries: 3, // 余额检查重试次数
// === 回滚参数 (v5.1新增) ===
maxRollbackRetries: 3, // 回滚最大重试次数
// === 原有参数 ===
checkInterval: 1000,
orderAmount: 0.05,
minDaysToDelivery: 7,
marginLevel: 10,
// === 数据管理参数 ===
maxHistoryLength: 5000,
minHistoryForTest: 200,
// === 合约筛选 ===
allowedCoins: ['BTC'],
};
// ==================== 全局数据存储 ====================
let contractPairs = [];
let priceHistory = {};
let positionRecords = {};
let closedTrades = [];
let stationarityCache = {};
let stationarityFailCount = {};
let lastStationarityCheck = {};
let nextStationarityCheckTime = 0;
let strategyStartTime = 0;
let pairCooldowns = {};
let opportunityLostCooldowns = {};
let accumulatedProfit = 0;
// ==================== 工具函数(数学计算)====================
function mean(arr) {
const s = arr.reduce((a,b)=>a+b,0);
return s/arr.length;
}
function variance(arr) {
const m = mean(arr);
return arr.reduce((s,x)=>s + (x-m)*(x-m), 0) / (arr.length-1);
}
function dot(a,b){
let s=0; for(let i=0;i<a.length;i++) s+=a[i]*b[i]; return s;
}
function ols(X, Y) {
const n = Y.length;
const k = X[0].length;
const XtX = Array.from({length:k}, ()=>Array(k).fill(0));
const XtY = Array(k).fill(0);
for(let i=0;i<n;i++){
for(let j=0;j<k;j++){
XtY[j] += X[i][j] * Y[i];
for(let l=0;l<k;l++) XtX[j][l] += X[i][j]*X[i][l];
}
}
function invertMatrix(A){
const m = A.length;
const B = A.map(row=>row.slice());
const I = Array.from({length:m}, (_,i)=>Array.from({length:m}, (__,j)=> i===j?1:0));
for(let i=0;i<m;i++){
let piv = i;
for(let r=i;r<m;r++) if(Math.abs(B[r][i])>Math.abs(B[piv][i])) piv=r;
if(Math.abs(B[piv][i])<1e-12) throw new Error('Singular matrix');
[B[i], B[piv]] = [B[piv], B[i]];
[I[i], I[piv]] = [I[piv], I[i]];
const div = B[i][i];
for(let c=0;c<m;c++){ B[i][c] /= div; I[i][c] /= div; }
for(let r=0;r<m;r++) if(r!==i){
const f = B[r][i];
for(let c=0;c<m;c++){ B[r][c] -= f*B[i][c]; I[r][c] -= f*I[i][c]; }
}
}
return I;
}
const XtXinv = invertMatrix(XtX);
const beta = Array(k).fill(0);
for(let j=0;j<k;j++) for(let l=0;l<k;l++) beta[j] += XtXinv[j][l]*XtY[l];
const res = Array(n).fill(0);
for(let i=0;i<n;i++){
let pred=0; for(let j=0;j<k;j++) pred += X[i][j]*beta[j];
res[i] = Y[i]-pred;
}
const rss = res.reduce((s,r)=>s+r*r,0);
const sigma2 = rss / (n - k);
const cov = XtXinv.map(row=>row.map(v=>v*sigma2));
return {beta, cov, sigma2, res};
}
function tStat(beta, cov, idx){
const se = Math.sqrt(Math.max(1e-16, cov[idx][idx]));
return beta[idx]/se;
}
function normalCdf(x){
return 0.5*(1 + erf(x/Math.sqrt(2)));
}
function erf(x){
const sign = x<0?-1:1; x = Math.abs(x);
const a1= 0.254829592;
const a2= -0.284496736;
const a3= 1.421413741;
const a4= -1.453152027;
const a5= 1.061405429;
const p= 0.3275911;
const t = 1.0/(1.0 + p*x);
const y = 1.0 - (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-x*x);
return sign*y;
}
function tPValue(t, df){
const z = Math.abs(t);
return 2*(1 - normalCdf(z));
}
function adfTest(series, maxLag=null){
const n = series.length;
if(n<10) throw new Error('series too short');
if(maxLag===null) maxLag = Math.floor(12*Math.pow(n/100, 1/4));
const dX = Array(n-1).fill(0);
for(let i=1;i<n;i++) dX[i-1] = series[i]-series[i-1];
const p = maxLag;
const rows = [];
const Ys = [];
for(let t=p+1; t<n; t++){
const row = [];
row.push(1);
row.push(series[t-1]);
for(let j=1;j<=p;j++) row.push( dX[t-1-j] );
rows.push(row);
Ys.push(dX[t-1]);
}
if(rows.length <= rows[0].length) throw new Error('not enough observations');
const res = ols(rows, Ys);
const tstat = tStat(res.beta, res.cov, 1);
const df = Ys.length - res.beta.length;
const pval = tPValue(tstat, df);
return { tStat: tstat, pValue: pval, usedLag: p };
}
function parseDeliveryDateFromString(dateStr) {
if (!dateStr || dateStr.length !== 6) return null;
let year = parseInt('20' + dateStr.substring(0, 2));
let month = parseInt(dateStr.substring(2, 4)) - 1;
let day = parseInt(dateStr.substring(4, 6));
let date = new Date(Date.UTC(year, month, day, 16, 0, 0, 0));
return date.getTime();
}
// ==================== 数据层 (DataLayer) ====================
const DataLayer = {
/**
* 获取盘口中间价(带重试机制)
*/
getDepthMidPrice: function(exchange, symbol, logDetail = false, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
let depth = exchange.GetDepth(symbol);
if (!depth || !depth.Bids || depth.Bids.length === 0 || !depth.Asks || depth.Asks.length === 0) {
if (attempt < maxRetries) {
Log(`⚠️ 获取${symbol}盘口失败,第${attempt}次重试...`);
Sleep(100);
continue;
}
Log(`❌ 获取${symbol}盘口失败(已重试${maxRetries}次),无法继续`);
return null;
}
let bestBid = depth.Bids[0].Price;
let bestAsk = depth.Asks[0].Price;
let midPrice = (bestBid + bestAsk) / 2;
if (logDetail) {
let spread = bestAsk - bestBid;
let spreadRate = spread / midPrice * 100;
Log(`📊 ${symbol} 盘口: Bid=${bestBid.toFixed(2)}, Ask=${bestAsk.toFixed(2)}, Mid=${midPrice.toFixed(2)}, Spread=${spread.toFixed(2)} (${spreadRate.toFixed(3)}%)`);
}
return midPrice;
} catch(e) {
if (attempt < maxRetries) {
Log(`⚠️ 获取${symbol}盘口异常(第${attempt}次): ${e.message},正在重试...`);
Sleep(500);
continue;
}
Log(`❌ 获取${symbol}盘口异常(已重试${maxRetries}次): ${e.message}`);
return null;
}
}
return null;
},
/**
* 初始化合约配对
*/
initContractPairs: function() {
// 预热GetTicker
exchanges[0].GetTicker('BTC_USDT');
exchanges[0].GetTicker('ETH_USDT');
exchanges[1].GetTicker('BTC_USDT.quarter');
exchanges[1].GetTicker('BTC_USDT.next_quarter');
let spotMarkets = exchanges[0].GetMarkets();
let futureMarkets = exchanges[1].GetMarkets();
if (!spotMarkets || !futureMarkets) {
Log('获取市场信息失败');
return false;
}
Log('现货市场总数:', Object.keys(spotMarkets).length);
Log('交割合约市场总数:', Object.keys(futureMarkets).length);
let spotContracts = [];
let deliveryContracts = [];
for (let key in spotMarkets) {
let market = spotMarkets[key];
let lowerKey = key.toLowerCase();
if (lowerKey.endsWith('_usdt')) {
spotContracts.push({ key: key, market: market });
}
}
for (let key in futureMarkets) {
let market = futureMarkets[key];
let lowerKey = key.toLowerCase();
if (lowerKey.includes('_usdt.') && !lowerKey.endsWith('.swap')) {
deliveryContracts.push({ key: key, market: market });
}
}
Log('找到现货合约数量:', spotContracts.length);
Log('找到交割合约数量:', deliveryContracts.length);
contractPairs = [];
for (let delivery of deliveryContracts) {
let lowerKey = delivery.key.toLowerCase();
let coinName = lowerKey.split('_usdt.')[0];
if (CONFIG.allowedCoins.length > 0 &&
!CONFIG.allowedCoins.includes(coinName.toUpperCase())) {
continue;
}
let spotKey = coinName + '_usdt';
let matchedSpot = spotContracts.find(s => s.key.toLowerCase() === spotKey);
if (matchedSpot) {
let deliveryDateStr = null;
let deliveryDate = null;
if (delivery.market.Symbol) {
let symbol = delivery.market.Symbol;
let match = symbol.match(/(\d{6})$/);
if (match) {
deliveryDateStr = match[1];
deliveryDate = parseDeliveryDateFromString(deliveryDateStr);
}
}
if (!deliveryDate) {
Log('⚠️ 无法解析交割日期,跳过:', delivery.key);
continue;
}
let daysToDelivery = (deliveryDate - Date.now()) / (1000 * 60 * 60 * 24);
if (daysToDelivery < CONFIG.minDaysToDelivery) {
Log(`⚠️ ${coinName.toUpperCase()} 交割日太近(${daysToDelivery.toFixed(1)}天),跳过`);
continue;
}
let deliveryType = lowerKey.split('_usdt.')[1];
let deliveryTypeMap = {
'this_week': '本周交割',
'next_week': '次周交割',
'quarter': '当季交割',
'next_quarter': '次季交割',
'bi_quarter': '双季交割',
'month': '本月交割',
'next_month': '下月交割'
};
let deliveryTypeName = deliveryTypeMap[deliveryType] || deliveryType.toUpperCase();
let ctVal = delivery.market.CtVal || 1;
contractPairs.push({
coin: coinName.toUpperCase(),
spotSymbol: matchedSpot.key,
deliverySymbol: delivery.key,
deliveryType: deliveryType,
deliveryTypeName: deliveryTypeName,
deliveryDateStr: deliveryDateStr,
deliveryDate: deliveryDate,
daysToDelivery: daysToDelivery,
ctVal: ctVal,
spotPrice: 0,
deliveryPrice: 0,
spread: 0,
spreadRate: 0
});
priceHistory[delivery.key] = [];
stationarityFailCount[delivery.key] = 0;
Log('✅ 配对成功:', coinName.toUpperCase(),
matchedSpot.key, '<->', delivery.key,
'| 交割日期:', deliveryDateStr,
'| 剩余天数:', daysToDelivery.toFixed(1),
'| 合约价值(CtVal):', ctVal);
}
}
Log('成功配对合约数量:', contractPairs.length);
Log('允许交易币种:', CONFIG.allowedCoins.join(', '));
return contractPairs.length > 0;
},
/**
* 更新所有合约价格和历史数据
*/
updatePrices: function() {
for (let pair of contractPairs) {
let spotTicker = exchanges[0].GetTicker(pair.spotSymbol);
if (!spotTicker || spotTicker.length === 0) return false;
let deliveryTicker = exchanges[1].GetTicker(pair.deliverySymbol);
if (!deliveryTicker || deliveryTicker.length === 0) return false;
pair.spotPrice = spotTicker.Last;
pair.deliveryPrice = deliveryTicker.Last;
pair.spread = pair.deliveryPrice - pair.spotPrice;
pair.spreadRate = pair.spread / pair.spotPrice;
pair.daysToDelivery = (pair.deliveryDate - Date.now()) / (1000 * 60 * 60 * 24);
try {
if (!priceHistory[pair.deliverySymbol]) {
priceHistory[pair.deliverySymbol] = [];
}
let existingHistory = priceHistory[pair.deliverySymbol];
if (existingHistory.length === 0) {
let spotRecords = exchanges[0].GetRecords(pair.spotSymbol, PERIOD_M1, 2000);
if (!spotRecords || spotRecords.length === 0) {
Log(`⚠️ 获取 ${pair.spotSymbol} 历史K线失败`);
continue;
}
Sleep(1000);
let deliveryRecords = exchanges[1].GetRecords(pair.deliverySymbol, PERIOD_M1, 2000);
if (!deliveryRecords || deliveryRecords.length === 0) {
Log(`⚠️ 获取 ${pair.deliverySymbol} 历史K线失败`);
continue;
} else {
Log(`✅ ${pair.deliverySymbol} 加载了 ${deliveryRecords.length} 根历史K线`);
}
let minLength = Math.min(spotRecords.length, deliveryRecords.length);
let spreadHistory = [];
for (let i = 0; i < minLength; i++) {
let spotClose = spotRecords[i].Close;
let deliveryClose = deliveryRecords[i].Close;
let klineTime = spotRecords[i].Time;
let spread = deliveryClose - spotClose;
let spreadRate = spread / spotClose;
spreadHistory.push({
time: klineTime,
spreadRate: spreadRate,
spread: spread,
spotPrice: spotClose,
deliveryPrice: deliveryClose
});
}
priceHistory[pair.deliverySymbol] = spreadHistory;
} else {
let spotRecords = exchanges[0].GetRecords(pair.spotSymbol, PERIOD_M1, 10);
if (!spotRecords || spotRecords.length === 0) continue;
let deliveryRecords = exchanges[1].GetRecords(pair.deliverySymbol, PERIOD_M1, 10);
if (!deliveryRecords || deliveryRecords.length === 0) continue;
let minLength = Math.min(spotRecords.length, deliveryRecords.length);
let lastHistoryTime = existingHistory[existingHistory.length - 1].time;
for (let i = 0; i < minLength; i++) {
let klineTime = spotRecords[i].Time;
if (klineTime > lastHistoryTime) {
let spotClose = spotRecords[i].Close;
let deliveryClose = deliveryRecords[i].Close;
let spread = deliveryClose - spotClose;
let spreadRate = spread / spotClose;
existingHistory.push({
time: klineTime,
spreadRate: spreadRate,
spread: spread,
spotPrice: spotClose,
deliveryPrice: deliveryClose
});
}
}
if (existingHistory.length > CONFIG.maxHistoryLength) {
let excess = existingHistory.length - CONFIG.maxHistoryLength;
existingHistory.splice(0, excess);
}
}
} catch (e) {
Log(`❌ 处理 ${pair.deliverySymbol} 历史数据异常:`, e.message);
}
}
return true;
},
/**
* 持久化保存历史数据
*/
saveHistoryData: function() {
let compactHistory = {};
for (let symbol in priceHistory) {
let history = priceHistory[symbol];
compactHistory[symbol] = history.slice(-2000);
}
_G('priceHistory', compactHistory);
_G('stationarityFailCount', stationarityFailCount);
_G('pairCooldowns', pairCooldowns);
_G('opportunityLostCooldowns', opportunityLostCooldowns);
},
/**
* 加载持久化数据
*/
loadHistoryData: function() {
try {
let savedHistory = _G('priceHistory');
if (savedHistory) {
priceHistory = savedHistory;
Log('📥 已恢复历史数据');
}
let savedFailCount = _G('stationarityFailCount');
if (savedFailCount) {
stationarityFailCount = savedFailCount;
Log('📥 已恢复平稳性计数');
}
let savedClosedTrades = _G('closedTrades');
if (savedClosedTrades) {
closedTrades = savedClosedTrades;
Log('📥 已恢复历史交易记录:', closedTrades.length, '笔');
}
let savedCooldowns = _G('pairCooldowns');
if (savedCooldowns) {
pairCooldowns = savedCooldowns;
let now = Date.now();
let activeCooldowns = 0;
for (let symbol in pairCooldowns) {
if (pairCooldowns[symbol] > now) activeCooldowns++;
}
Log('📥 已恢复10分钟冷却期数据:', activeCooldowns, '个交易对处于冷却中');
}
let savedOpportunityCooldowns = _G('opportunityLostCooldowns');
if (savedOpportunityCooldowns) {
opportunityLostCooldowns = savedOpportunityCooldowns;
let now = Date.now();
let activeOpportunityCooldowns = 0;
for (let symbol in opportunityLostCooldowns) {
if (opportunityLostCooldowns[symbol] > now) activeOpportunityCooldowns++;
}
Log('📥 已恢复套利机会消失冷却数据:', activeOpportunityCooldowns, '个交易对处于1分钟冷却中');
}
return true;
} catch(e) {
Log('⚠️ 加载历史数据失败:', e.message);
}
return false;
},
/**
* 初始化账户记录
*/
initializeAccountRecords: function() {
exchanges[0].SetCurrency(contractPairs[0].spotSymbol);
let spotAccount = exchanges[0].GetAccount();
let futureAccount = exchanges[1].GetAccount();
if (!spotAccount || !futureAccount) {
Log('❌ 获取初始账户信息失败');
return false;
}
let savedProfit = _G('accumulatedProfit');
if (savedProfit !== null && savedProfit !== undefined) {
accumulatedProfit = savedProfit;
Log('📥 恢复累计盈亏:', accumulatedProfit.toFixed(4), 'USDT');
}
Log('💰 策略账户信息:');
Log(' 现货USDT余额:', spotAccount.Balance.toFixed(4));
Log(' 期货权益:', futureAccount.Equity.toFixed(4));
Log(' 累计盈亏:', accumulatedProfit.toFixed(4), 'USDT');
return true;
}
};
// ==================== 信号层 (SignalLayer) ====================
const SignalLayer = {
/**
* ADF平稳性检验
*/
evaluateSpreadStationarity: function(deliverySymbol) {
let history = priceHistory[deliverySymbol];
if (!history || history.length < CONFIG.minHistoryForTest) {
return {
isStationary: false,
canTrade: false,
reason: `数据不足(${history ? history.length : 0}/${CONFIG.minHistoryForTest})`,
dataLength: history ? history.length : 0,
consecutiveFails: stationarityFailCount[deliverySymbol] || 0,
timestamp: Date.now()
};
}
let now = Date.now();
if (stationarityCache[deliverySymbol]) {
let lastCheck = lastStationarityCheck[deliverySymbol] || 0;
if (now - lastCheck < CONFIG.stationarityCheckInterval) {
return stationarityCache[deliverySymbol];
}
}
let spreadRates = history.map(h => h.spreadRate);
let adfRes = null;
let adfPass = false;
try {
let testWindow = Math.min(CONFIG.adfWindow, spreadRates.length);
let testSeries = spreadRates.slice(-testWindow);
adfRes = adfTest(testSeries);
adfPass = adfRes.pValue < CONFIG.adfPValueThreshold;
} catch(e) {
Log(`❌ ${deliverySymbol} ADF检验异常:`, e.message);
return {
isStationary: false,
canTrade: false,
reason: 'ADF检验失败',
dataLength: spreadRates.length,
consecutiveFails: stationarityFailCount[deliverySymbol] || 0,
timestamp: now
};
}
if (!adfPass) {
stationarityFailCount[deliverySymbol] = (stationarityFailCount[deliverySymbol] || 0) + 1;
} else {
stationarityFailCount[deliverySymbol] = 0;
}
let consecutiveFails = stationarityFailCount[deliverySymbol];
let canTrade = consecutiveFails < CONFIG.consecutiveFailThreshold;
let finalReason = '';
if (adfPass && canTrade) {
finalReason = '✅ 平稳,可交易';
} else if (adfPass && !canTrade) {
finalReason = `⚠️ 当前平稳,但之前连续${consecutiveFails}次不平稳`;
} else if (!adfPass && canTrade) {
finalReason = `⚠️ 当前不平稳(${consecutiveFails}/${CONFIG.consecutiveFailThreshold}),仍可交易`;
} else {
finalReason = `❌ 禁止交易 - 连续${consecutiveFails}次不平稳`;
}
let result = {
isStationary: adfPass,
canTrade: canTrade,
reason: finalReason,
dataLength: spreadRates.length,
adf: adfRes,
consecutiveFails: consecutiveFails,
timestamp: now
};
stationarityCache[deliverySymbol] = result;
lastStationarityCheck[deliverySymbol] = now;
return result;
},
/**
* 定时触发平稳性检验
*/
runStationarityChecks: function() {
let now = Date.now();
if (now < nextStationarityCheckTime) return;
for (let pair of contractPairs) {
if (pair.daysToDelivery < CONFIG.minDaysToDelivery) continue;
SignalLayer.evaluateSpreadStationarity(pair.deliverySymbol);
}
nextStationarityCheckTime = now + CONFIG.stationarityCheckInterval;
let nextCheckDate = new Date(nextStationarityCheckTime);
Log('⏰ 平稳性下次检验时间:', nextCheckDate.toLocaleString());
},
/**
* 计算Z-Score
*/
calculateZScore: function(deliverySymbol) {
let history = priceHistory[deliverySymbol];
if (!history || history.length < CONFIG.minHistoryForTest) return null;
let spreadRates = history.map(h => h.spreadRate);
let mu = mean(spreadRates);
let sigma = Math.sqrt(variance(spreadRates));
let currentSpreadRate = history[history.length - 1].spreadRate;
let z = (currentSpreadRate - mu) / (sigma || 1e-6);
return {
zScore: z,
mean: mu,
std: sigma,
current: currentSpreadRate,
dataLength: spreadRates.length
};
},
/**
* 判断价格偏离方向
*/
detectPriceDeviation: function(pair, zInfo) {
let history = priceHistory[pair.deliverySymbol];
if (!history || history.length < 100) {
return {
isSpotDeviation: false,
isDeliveryDeviation: false,
reason: '数据不足,无法判断偏离方向',
spotDeviationRate: 0,
deliveryDeviationRate: 0
};
}
let spotPrices = history.slice(-100).map(h => h.spotPrice);
let deliveryPrices = history.slice(-100).map(h => h.deliveryPrice);
let spotMean = mean(spotPrices);
let deliveryMean = mean(deliveryPrices);
let spotDeviationRate = Math.abs((pair.spotPrice - spotMean) / spotMean);
let deliveryDeviationRate = Math.abs((pair.deliveryPrice - deliveryMean) / deliveryMean);
Log(`📊 偏离分析: 现货偏离=${(spotDeviationRate*100).toFixed(3)}%, 期货偏离=${(deliveryDeviationRate*100).toFixed(3)}%`);
if (spotDeviationRate > deliveryDeviationRate) {
return {
isSpotDeviation: true,
isDeliveryDeviation: false,
reason: `现货偏离更大(${(spotDeviationRate*100).toFixed(3)}% > ${(deliveryDeviationRate*100).toFixed(3)}%)`,
spotDeviationRate: spotDeviationRate,
deliveryDeviationRate: deliveryDeviationRate
};
} else if (deliveryDeviationRate > spotDeviationRate) {
return {
isSpotDeviation: false,
isDeliveryDeviation: true,
reason: `期货偏离更大(${(deliveryDeviationRate*100).toFixed(3)}% > ${(spotDeviationRate*100).toFixed(3)}%)`,
spotDeviationRate: spotDeviationRate,
deliveryDeviationRate: deliveryDeviationRate
};
} else {
return {
isSpotDeviation: false,
isDeliveryDeviation: false,
reason: '双方偏离相同或偏离不明显',
spotDeviationRate: spotDeviationRate,
deliveryDeviationRate: deliveryDeviationRate
};
}
},
/**
* 判断是否开仓
*/
shouldOpenPosition: function(pair) {
let stationarity = stationarityCache[pair.deliverySymbol];
if (!stationarity || !stationarity.canTrade) {
return {
should: false,
reason: stationarity ? ('不可交易: ' + stationarity.reason) : '等待平稳性检验',
detail: stationarity
};
}
let zInfo = SignalLayer.calculateZScore(pair.deliverySymbol);
if (!zInfo) {
return {
should: false,
reason: '无法计算Z-Score',
detail: null
};
}
let absZ = Math.abs(zInfo.zScore);
if (absZ < CONFIG.zScoreEntry) {
return {
should: false,
reason: `Z-Score未偏离(|z|=${absZ.toFixed(2)} < ${CONFIG.zScoreEntry})`,
detail: zInfo
};
}
let direction = zInfo.zScore > 0 ? 'positive' : 'negative';
let deviation = SignalLayer.detectPriceDeviation(pair, zInfo);
let priceSnapshot = {
spotPrice: pair.spotPrice,
deliveryPrice: pair.deliveryPrice,
spread: pair.spread,
spreadRate: pair.spreadRate
};
let mean = zInfo.mean;
let std = zInfo.std;
let targetSpreadRateUpper = mean + CONFIG.zScoreExitTarget * std;
let targetSpreadRateLower = mean - CONFIG.zScoreExitTarget * std;
let targetSpreadUpper = targetSpreadRateUpper * pair.spotPrice;
let targetSpreadLower = targetSpreadRateLower * pair.spotPrice;
let maxDeviationZ = absZ * (1 + CONFIG.zScoreMaxDeviation);
let maxDeviationSpreadRate = mean + (zInfo.zScore > 0 ? 1 : -1) * maxDeviationZ * std;
let maxDeviationSpread = maxDeviationSpreadRate * pair.spotPrice;
let history = priceHistory[pair.deliverySymbol];
let spreads = history.slice(-100).map(h => h.spread);
let spreadSum = spreads.reduce((a, b) => a + b, 0);
let avgSpread = spreadSum / spreads.length;
return {
should: true,
reason: `✅ Z-Score偏离正常区间 z=${zInfo.zScore.toFixed(2)}`,
direction: direction,
deviation: deviation,
priceSnapshot: priceSnapshot,
openZScore: zInfo.zScore,
openMean: mean,
openStd: std,
targetSpreadUpper: targetSpreadUpper,
targetSpreadLower: targetSpreadLower,
maxDeviationSpread: maxDeviationSpread,
avgSpread: avgSpread,
detail: {
stationarity: stationarity,
zInfo: zInfo
}
};
},
/**
* 判断是否平仓(去掉止盈,只保留止损)
*/
shouldClosePosition: function(deliverySymbol, currentPair) {
let record = positionRecords[deliverySymbol];
if (!record) return { should: false };
let deliveryPos = ExecutionLayer.getPositionBySymbol(deliverySymbol);
if (!deliveryPos) return { should: false };
let pnl = SignalLayer.calculateUnrealizedPnL(record, currentPair);
if (!pnl) return { should: false };
let currentSpread = pnl.currentDeliveryPrice - pnl.currentSpotPrice;
// 价差回归判断
if (currentSpread >= record.targetSpreadLower &&
currentSpread <= record.targetSpreadUpper) {
return {
should: true,
reason: '✅ 价差回归目标区间',
detail: `判断时Depth价差=${currentSpread.toFixed(2)} ∈ [${record.targetSpreadLower.toFixed(2)}, ${record.targetSpreadUpper.toFixed(2)}] | ` +
`实时盈亏: ${(pnl.totalPnlRate*100).toFixed(4)}%`,
priceSnapshot: {
snapshotSpotPrice: pnl.currentSpotPrice,
snapshotDeliveryPrice: pnl.currentDeliveryPrice,
snapshotSpread: currentSpread,
snapshotTime: Date.now()
}
};
}
// ✅ 只保留止损,去掉止盈
if (pnl.totalPnlRate <= -CONFIG.stopLossRate) {
return {
should: true,
reason: '🛑 达到止损线(需二次确认)',
detail: `实时亏损 ${(pnl.totalPnlRate*100).toFixed(4)}% <= ${(-CONFIG.stopLossRate*100).toFixed(2)}%`,
needsConfirmation: true, // ✅ 标记需要二次确认
priceSnapshot: {
snapshotSpotPrice: pnl.currentSpotPrice,
snapshotDeliveryPrice: pnl.currentDeliveryPrice,
snapshotSpread: currentSpread,
snapshotTime: Date.now()
}
};
}
// 临近交割
if (currentPair.daysToDelivery < 1) {
return {
should: true,
reason: '⏰ 临近交割',
detail: `剩余 ${currentPair.daysToDelivery.toFixed(1)} 天 | 实时盈亏: ${(pnl.totalPnlRate*100).toFixed(4)}%`,
priceSnapshot: {
snapshotSpotPrice: pnl.currentSpotPrice,
snapshotDeliveryPrice: pnl.currentDeliveryPrice,
snapshotSpread: currentSpread,
snapshotTime: Date.now()
}
};
}
// 持仓过久
if (record.openTime) {
let holdDays = (Date.now() - record.openTime) / (1000 * 60 * 60 * 24);
if (holdDays > CONFIG.maxHoldDays && pnl.totalPnlRate > 0) {
return {
should: true,
reason: '⏳ 持仓过久',
detail: `持仓 ${holdDays.toFixed(1)} 天 > ${CONFIG.maxHoldDays} 天 | 盈利${(pnl.totalPnlRate*100).toFixed(4)}%`,
priceSnapshot: {
snapshotSpotPrice: pnl.currentSpotPrice,
snapshotDeliveryPrice: pnl.currentDeliveryPrice,
snapshotSpread: currentSpread,
snapshotTime: Date.now()
}
};
}
}
return { should: false };
},
/**
* 计算实时盈亏
*/
calculateUnrealizedPnL: function(record, currentPair) {
if (!record || !currentPair) return null;
let currentSpotPrice = DataLayer.getDepthMidPrice(exchanges[0], currentPair.spotSymbol);
let currentDeliveryPrice = DataLayer.getDepthMidPrice(exchanges[1], currentPair.deliverySymbol);
if (!currentSpotPrice || !currentDeliveryPrice) {
currentSpotPrice = currentPair.spotPrice;
currentDeliveryPrice = currentPair.deliveryPrice;
}
let spotReturnRate = 0;
let deliveryReturnRate = 0;
if (record.direction === 'positive') {
spotReturnRate = (currentSpotPrice - record.openSpotPrice) / record.openSpotPrice;
deliveryReturnRate = (record.openDeliveryPrice - currentDeliveryPrice) / record.openDeliveryPrice;
} else {
spotReturnRate = (record.openSpotPrice - currentSpotPrice) / record.openSpotPrice;
deliveryReturnRate = (currentDeliveryPrice - record.openDeliveryPrice) / record.openDeliveryPrice;
}
let totalReturnRate = spotReturnRate + deliveryReturnRate;
let requiredUSD = record.openSpotPrice * record.spotAmount;
let totalPnl = totalReturnRate * requiredUSD;
return {
totalPnl: totalPnl,
totalPnlRate: totalReturnRate,
spotReturnRate: spotReturnRate,
deliveryReturnRate: deliveryReturnRate,
spotPnl: spotReturnRate * requiredUSD,
deliveryPnl: deliveryReturnRate * requiredUSD,
currentSpotPrice: currentSpotPrice,
currentDeliveryPrice: currentDeliveryPrice
};
}
};
// ==================== 执行层 (ExecutionLayer) ====================
const ExecutionLayer = {
/**
* 检查现货账户余额
*/
checkSpotBalance: function(pair, direction, amount, currentPrice) {
Log('🔍 检查现货账户余额...');
for (let attempt = 1; attempt <= CONFIG.balanceCheckRetries; attempt++) {
try {
exchanges[0].SetCurrency(pair.spotSymbol);
let account = exchanges[0].GetAccount();
if (!account) {
if (attempt < CONFIG.balanceCheckRetries) {
Log(`⚠️ 获取账户信息失败,第${attempt}次重试...`);
Sleep(500);
continue;
}
Log('❌ 无法获取现货账户信息');
return {
success: false,
reason: '无法获取账户信息',
canRetry: true
};
}
if (direction === 'buy') {
let requiredUSDT = amount * currentPrice;
let availableUSDT = account.Balance;
Log(` 操作类型: 买入${pair.coin}`);
Log(` 需要USDT: ${requiredUSDT.toFixed(4)}`);
Log(` 可用USDT: ${availableUSDT.toFixed(4)}`);
Log(` 保留USDT: ${CONFIG.minUSDTReserve.toFixed(4)}`);
if (availableUSDT < requiredUSDT + CONFIG.minUSDTReserve) {
Log(`⚠️ USDT余额不足,无法买入`);
Log(` 缺口: ${(requiredUSDT + CONFIG.minUSDTReserve - availableUSDT).toFixed(4)} USDT`);
Log(` 💡 策略提示:将等待卖出方向的套利机会`);
return {
success: false,
reason: `USDT不足(需要${requiredUSDT.toFixed(2)}, 可用${availableUSDT.toFixed(2)})`,
insufficientDirection: 'buy',
canRetry: false
};
}
Log(`✅ USDT余额充足`);
return { success: true };
} else if (direction === 'sell') {
let requiredCoin = amount;
let availableCoin = account.Stocks;
Log(` 操作类型: 卖出${pair.coin}`);
Log(` 需要${pair.coin}: ${requiredCoin.toFixed(6)}`);
Log(` 可用${pair.coin}: ${availableCoin.toFixed(6)}`);
if (availableCoin < requiredCoin) {
Log(`⚠️ ${pair.coin}余额不足,无法卖出`);
Log(` 缺口: ${(requiredCoin - availableCoin).toFixed(6)} ${pair.coin}`);
Log(` 💡 策略提示:将等待买入方向的套利机会`);
return {
success: false,
reason: `${pair.coin}不足(需要${requiredCoin.toFixed(6)}, 可用${availableCoin.toFixed(6)})`,
insufficientDirection: 'sell',
canRetry: false
};
}
Log(`✅ ${pair.coin}余额充足`);
return { success: true };
}
} catch(e) {
if (attempt < CONFIG.balanceCheckRetries) {
Log(`⚠️ 检查余额异常(第${attempt}次): ${e.message},正在重试...`);
Sleep(500);
continue;
}
Log(`❌ 检查余额异常: ${e.message}`);
return {
success: false,
reason: '检查余额异常: ' + e.message,
canRetry: true
};
}
}
return {
success: false,
reason: '检查余额超时',
canRetry: true
};
},
/**
* 通用下单函数
*/
createOrderWithFallback: function(exchange, symbol, direction, amount, limitPrice, orderType, maxRetry = 3) {
let orderId = null;
let order = null;
let retryCount = 0;
let useMarketOrder = (limitPrice === -1);
let isSpotBuy = (orderType === 'spot' || orderType === '现货') && direction === 'buy';
function getActualAmount(useMarket) {
if (isSpotBuy && useMarket) {
let currentPrice = DataLayer.getDepthMidPrice(exchange, symbol);
if (!currentPrice) {
Log(`❌ 无法获取${symbol}盘口价格用于计算USDT金额`);
return 0;
}
let usdtAmount = amount * currentPrice;
Log(` 💡 现货买单转换: ${amount.toFixed(6)} 币 → ${usdtAmount.toFixed(4)} USDT (Depth价格${currentPrice.toFixed(2)})`);
return usdtAmount;
}
return amount;
}
if (!useMarketOrder) {
let orderAmount = amount;
Log(`📝 提交${orderType}限价${direction}单: 价格=${limitPrice.toFixed(2)}, 数量=${orderAmount.toFixed(6)}`);
orderId = exchange.CreateOrder(symbol, direction, limitPrice, orderAmount);
if (!orderId) {
Log(`❌ ${orderType}限价单提交失败,改用市价单`);
useMarketOrder = true;
} else {
Log(`✅ ${orderType}限价单已提交, OrderId:`, orderId);
}
}
if (useMarketOrder && !orderId) {
let marketAmount = getActualAmount(true);
if (marketAmount === 0) return null;
Log(`📝 提交${orderType}市价${direction}单, 数量=${marketAmount.toFixed(6)}`);
orderId = exchange.CreateOrder(symbol, direction, -1, marketAmount);
if (!orderId) {
Log(`❌ ${orderType}市价单提交失败`);
return null;
}
Log(`✅ ${orderType}市价单已提交, OrderId:`, orderId);
}
Sleep(3000);
order = exchange.GetOrder(orderId);
while ((!order || order.Status === 2 || order.Status === 3) && retryCount < maxRetry) {
retryCount++;
if (!order) {
Log(`⚠️ 无法获取订单状态,第${retryCount}/${maxRetry}次重试...`);
} else if (order.Status === 2) {
Log(`⚠️ 订单被撤销,第${retryCount}/${maxRetry}次重试...`);
} else if (order.Status === 3) {
Log(`⚠️ 订单状态未知,第${retryCount}/${maxRetry}次重试...`);
}
let retryAmount = getActualAmount(true);
if (retryAmount === 0) return null;
Log(`🔄 重试使用市价单, 数量=${retryAmount.toFixed(6)}`);
orderId = exchange.CreateOrder(symbol, direction, -1, retryAmount);
if (!orderId) {
Log(`❌ 第${retryCount}次重试下单失败`);
Sleep(2000);
continue;
}
Sleep(3000);
order = exchange.GetOrder(orderId);
if (order && order.Status === 1) {
Log(`✅ 第${retryCount}次重试成功`);
break;
}
if (order) {
Log(`❌ 第${retryCount}次重试订单状态: ${order.Status}`);
}
}
if (!order) {
Log(`❌ ${orderType}订单无法获取,最终失败`);
return null;
}
if (order.Status !== 1) {
Log(`❌ ${orderType}订单最终未成交,状态: ${order.Status}`);
let currentPrice = DataLayer.getDepthMidPrice(exchange, order.Symbol);
if (currentPrice > 0) {
Log(` 当前市场价格(Depth): ${currentPrice.toFixed(2)}`);
if (order.Price && order.Price > 0) {
Log(` 订单价格: ${order.Price.toFixed(2)}`);
Log(` 价格偏差: ${((order.Price - currentPrice) / currentPrice * 100).toFixed(3)}%`);
}
}
if (order.Status === 0) {
Log(`⚠️ 订单仍在等待中,尝试撤销...`);
exchange.CancelOrder(orderId);
}
return null;
}
Log(`✅ ${orderType}订单最终成交`);
return order;
},
/**
* 获取期货持仓
*/
getActualPositions: function() {
let futurePositions = exchanges[1].GetPositions('USDT.futures');
if (!futurePositions) futurePositions = [];
return futurePositions;
},
/**
* 根据Symbol获取持仓
*/
getPositionBySymbol: function(deliverySymbol) {
let allPositions = ExecutionLayer.getActualPositions();
for (let pos of allPositions) {
if (Math.abs(pos.Amount) <= 0) continue;
if (pos.Symbol === deliverySymbol) return pos;
}
return null;
},
/**
* 计算合约数量
*/
calculateContractAmount: function(spotAmount, ctVal) {
let contractAmount = spotAmount / ctVal;
contractAmount = Math.floor(contractAmount);
if (contractAmount < 1) contractAmount = 1;
return contractAmount;
},
/**
* 冷却期管理
*/
addCooldown: function(deliverySymbol, coin, reason) {
pairCooldowns[deliverySymbol] = Date.now() + CONFIG.cooldownDuration;
Log(`⏸️ ${deliverySymbol} 进入10分钟冷却期`);
Log(` 原因: ${reason}`);
_G('pairCooldowns', pairCooldowns);
},
checkCooldown: function(deliverySymbol, coin) {
if (pairCooldowns[deliverySymbol]) {
let cooldownEnd = pairCooldowns[deliverySymbol];
let now = Date.now();
if (now < cooldownEnd) {
return true;
} else {
delete pairCooldowns[deliverySymbol];
_G('pairCooldowns', pairCooldowns);
return false;
}
}
return false;
},
addOpportunityLostCooldown: function(deliverySymbol, reason) {
opportunityLostCooldowns[deliverySymbol] = Date.now() + CONFIG.opportunityLostCooldown;
Log(`⏸️ ${deliverySymbol} 套利机会消失,进入1分钟冷却`);
Log(` 原因: ${reason}`);
_G('opportunityLostCooldowns', opportunityLostCooldowns);
},
checkOpportunityLostCooldown: function(deliverySymbol) {
if (opportunityLostCooldowns[deliverySymbol]) {
let cooldownEnd = opportunityLostCooldowns[deliverySymbol];
let now = Date.now();
if (now < cooldownEnd) {
return true;
} else {
delete opportunityLostCooldowns[deliverySymbol];
_G('opportunityLostCooldowns', opportunityLostCooldowns);
return false;
}
}
return false;
},
/**
* ✅ v5.1新增:回滚现货订单(带Depth价格验证)
*/
rollbackSpotOrder: function(exchange, pair, direction, amount, reason) {
Log('-----------------------------------');
Log('🔄 开始回滚现货订单');
Log(' 币种:', pair.coin);
Log(' 方向:', direction === 'buy' ? '买入' : '卖出');
Log(' 数量:', amount.toFixed(6));
Log(' 原因:', reason);
for (let retry = 1; retry <= CONFIG.maxRollbackRetries; retry++) {
Log(`🔄 回滚尝试 ${retry}/${CONFIG.maxRollbackRetries}`);
// 1. 获取当前Depth价格
let currentPrice = DataLayer.getDepthMidPrice(exchange, pair.spotSymbol, true);
if (!currentPrice) {
Log(`⚠️ 第${retry}次无法获取Depth价格,等待后重试...`);
Sleep(1000);
continue;
}
// 2. 提交回滚订单(市价单)
let rollbackOrderId = null;
if (direction === 'buy') {
let usdtAmount = amount * currentPrice;
Log(` 提交市价买单: ${usdtAmount.toFixed(4)} USDT (按Depth价格${currentPrice.toFixed(2)})`);
rollbackOrderId = exchange.CreateOrder(pair.spotSymbol, 'buy', -1, usdtAmount);
} else {
Log(` 提交市价卖单: ${amount.toFixed(6)} ${pair.coin}`);
rollbackOrderId = exchange.CreateOrder(pair.spotSymbol, 'sell', -1, amount);
}
if (!rollbackOrderId) {
Log(`❌ 第${retry}次回滚订单提交失败`);
Sleep(1000);
continue;
}
Log(` 回滚订单已提交: ${rollbackOrderId}`);
Sleep(2000);
// 3. 检查订单状态
let rollbackOrder = exchange.GetOrder(rollbackOrderId);
if (!rollbackOrder) {
Log(`⚠️ 第${retry}次无法获取回滚订单状态`);
continue;
}
Log(` 回滚订单状态: ${rollbackOrder.Status} (0=等待, 1=成交, 2=撤销)`);
if (rollbackOrder.Status === 1) {
Log('✅ 回滚订单成交');
DisplayLayer.printOrderDetails(exchange, pair.spotSymbol, rollbackOrderId, '现货回滚订单');
// 4. 验证回滚后仓位
exchange.SetCurrency(pair.spotSymbol);
let account = exchange.GetAccount();
if (account) {
if (direction === 'buy') {
Log(` 回滚后${pair.coin}余额: ${account.Stocks.toFixed(6)}`);
} else {
Log(` 回滚后USDT余额: ${account.Balance.toFixed(4)}`);
}
}
Log('✅ 现货回滚成功!');
return true;
}
if (rollbackOrder.Status === 0) {
Log(`⚠️ 第${retry}次回滚订单仍在等待,撤销后重试...`);
exchange.CancelOrder(rollbackOrderId);
Sleep(1000);
continue;
}
if (rollbackOrder.Status === 2) {
Log(`⚠️ 第${retry}次回滚订单被撤销,重试...`);
Sleep(1000);
continue;
}
}
Log('❌ 现货回滚失败!已尝试' + CONFIG.maxRollbackRetries + '次');
return false;
},
/**
* ✅ v5.1新增:回滚期货订单(带Depth价格验证)
*/
rollbackDeliveryOrder: function(exchange, pair, direction, amount, reason) {
Log('-----------------------------------');
Log('🔄 开始回滚期货订单');
Log(' 币种:', pair.coin);
Log(' 方向:', direction === 'closebuy' ? '平多' : '平空');
Log(' 数量:', amount);
Log(' 原因:', reason);
for (let retry = 1; retry <= CONFIG.maxRollbackRetries; retry++) {
Log(`🔄 回滚尝试 ${retry}/${CONFIG.maxRollbackRetries}`);
// 1. 获取当前Depth价格(用于日志)
let currentPrice = DataLayer.getDepthMidPrice(exchange, pair.deliverySymbol, true);
if (currentPrice) {
Log(` 当前Depth期货价: ${currentPrice.toFixed(2)}`);
}
// 2. 提交回滚平仓订单(市价单)
Log(` 提交市价${direction === 'closebuy' ? '平多' : '平空'}单: ${amount} 张`);
let rollbackOrderId = exchange.CreateOrder(pair.deliverySymbol, direction, -1, amount);
if (!rollbackOrderId) {
Log(`❌ 第${retry}次回滚订单提交失败`);
Sleep(1000);
continue;
}
Log(` 回滚订单已提交: ${rollbackOrderId}`);
Sleep(2000);
// 3. 检查订单状态
let rollbackOrder = exchange.GetOrder(rollbackOrderId);
if (!rollbackOrder) {
Log(`⚠️ 第${retry}次无法获取回滚订单状态`);
Sleep(1000);
continue;
}
Log(` 回滚订单状态: ${rollbackOrder.Status} (0=等待, 1=成交, 2=撤销)`);
if (rollbackOrder.Status === 1) {
Log('✅ 回滚订单成交');
DisplayLayer.printOrderDetails(exchange, pair.deliverySymbol, rollbackOrderId, '期货回滚订单');
// 4. 验证回滚后仓位
Sleep(1000);
let remainingPos = ExecutionLayer.getPositionBySymbol(pair.deliverySymbol);
if (remainingPos && Math.abs(remainingPos.Amount) > 0) {
Log(`⚠️ 警告:回滚后仍有残余仓位 ${remainingPos.Amount} 张`);
} else {
Log('✅ 验证:期货仓位已完全平仓');
}
Log('✅ 期货回滚成功!');
return true;
}
if (rollbackOrder.Status === 0) {
Log(`⚠️ 第${retry}次回滚订单仍在等待,撤销后重试...`);
exchange.CancelOrder(rollbackOrderId);
Sleep(1000);
continue;
}
if (rollbackOrder.Status === 2) {
Log(`⚠️ 第${retry}次回滚订单被撤销,重试...`);
Sleep(1000);
continue;
}
}
Log('❌ 期货回滚失败!已尝试' + CONFIG.maxRollbackRetries + '次');
return false;
},
/**
* ✅ v5.1改进:开仓执行(使用增强的回滚验证)
*/
openPosition: function(pair, openCheck) {
Log('===================================');
Log('🚀 开始开仓流程');
Log('===================================');
if (ExecutionLayer.checkCooldown(pair.deliverySymbol, pair.coin)) return false;
if (ExecutionLayer.checkOpportunityLostCooldown(pair.deliverySymbol)) return false;
// 检查并平掉现有仓位
Log('🔍 检查期货账户现有仓位...');
let existingPosition = ExecutionLayer.getPositionBySymbol(pair.deliverySymbol);
if (existingPosition && Math.abs(existingPosition.Amount) > 0) {
Log('⚠️ 检测到该合约的现有仓位,执行平仓操作...');
try {
let closeDirection = existingPosition.Type === PD_LONG ? 'closebuy' : 'closesell';
let closeAmount = Math.abs(existingPosition.Amount);
let closeOrder = ExecutionLayer.createOrderWithFallback(
exchanges[1], pair.deliverySymbol, closeDirection, closeAmount, -1, '期货'
);
if (!closeOrder) {
Log('❌ 平仓现有持仓失败,终止开仓');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '平仓现有持仓失败');
return false;
}
Log('✅ 成功平仓现有持仓');
Sleep(2000);
} catch (e) {
Log('❌ 平仓现有持仓异常:', e.message);
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '平仓现有持仓异常');
return false;
}
} else {
Log('✅ 期货账户无现有仓位,可以开仓');
}
// 二次验证套利机会
Log('🔄 重新获取实时Depth盘口价格并验证套利机会...');
Log('-----------------------------------');
let realtimeSpotPrice = DataLayer.getDepthMidPrice(exchanges[0], pair.spotSymbol, true);
let realtimeDeliveryPrice = DataLayer.getDepthMidPrice(exchanges[1], pair.deliverySymbol, true);
if (!realtimeSpotPrice || !realtimeDeliveryPrice) {
Log('❌ 获取实时Depth盘口价格失败,开仓终止');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '获取实时Depth价格失败');
return false;
}
let realtimeSpread = realtimeDeliveryPrice - realtimeSpotPrice;
let realtimeSpreadRate = realtimeSpread / realtimeSpotPrice;
Log('📊 价格对比(信号时 vs 实时Depth):');
Log(' 信号时现货价:', openCheck.priceSnapshot.spotPrice.toFixed(2),
'→ 实时Depth现货价:', realtimeSpotPrice.toFixed(2),
'| 偏差:', ((realtimeSpotPrice - openCheck.priceSnapshot.spotPrice) / openCheck.priceSnapshot.spotPrice * 100).toFixed(3), '%');
Log(' 信号时期货价:', openCheck.priceSnapshot.deliveryPrice.toFixed(2),
'→ 实时Depth期货价:', realtimeDeliveryPrice.toFixed(2),
'| 偏差:', ((realtimeDeliveryPrice - openCheck.priceSnapshot.deliveryPrice) / openCheck.priceSnapshot.deliveryPrice * 100).toFixed(3), '%');
let history = priceHistory[pair.deliverySymbol];
if (!history || history.length < CONFIG.minHistoryForTest) {
Log('❌ 历史数据不足,无法验证');
return false;
}
let mu = openCheck.openMean;
let sigma = openCheck.openStd;
let realtimeZScore = (realtimeSpreadRate - mu) / (sigma || 1e-6);
Log('📈 Z-Score 对比:');
Log(' 信号时 Z-Score:', openCheck.openZScore.toFixed(2));
Log(' 实时Depth Z-Score:', realtimeZScore.toFixed(2));
let absRealtimeZ = Math.abs(realtimeZScore);
if (absRealtimeZ < CONFIG.zScoreEntry) {
Log('❌ 套利机会已消失!');
Log(` 实时Depth |Z-Score| = ${absRealtimeZ.toFixed(2)} < ${CONFIG.zScoreEntry}`);
ExecutionLayer.addOpportunityLostCooldown(pair.deliverySymbol, `Z-Score不足: ${absRealtimeZ.toFixed(2)} < ${CONFIG.zScoreEntry}`);
return false;
}
let realtimeDirection = realtimeZScore > 0 ? 'positive' : 'negative';
if (realtimeDirection !== openCheck.direction) {
Log('❌ 价差方向已反转!');
ExecutionLayer.addOpportunityLostCooldown(pair.deliverySymbol, `方向反转: ${openCheck.direction} → ${realtimeDirection}`);
return false;
}
Log('✅ 实时Depth验证通过,套利机会仍然存在');
// 开仓前检查现货余额
Log('-----------------------------------');
let spotAmount = CONFIG.orderAmount;
let spotDirection = realtimeDirection === 'positive' ? 'buy' : 'sell';
let balanceCheck = ExecutionLayer.checkSpotBalance(pair, spotDirection, spotAmount, realtimeSpotPrice);
if (!balanceCheck.success) {
Log('⚠️ 现货余额不足,本次开仓跳过');
Log(' 原因:', balanceCheck.reason);
if (balanceCheck.insufficientDirection === 'buy') {
Log(' 💡 当前无法执行买入现货,将等待卖出现货的套利机会');
} else if (balanceCheck.insufficientDirection === 'sell') {
Log(' 💡 当前无法执行卖出现货,将等待买入现货的套利机会');
}
if (balanceCheck.canRetry) {
Log(' ⚠️ 检查余额异常,触发10分钟冷却');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '余额检查异常: ' + balanceCheck.reason);
}
return false;
}
Log('-----------------------------------');
// 计算开仓参数
let direction = realtimeDirection;
let targetSpreadRateUpper = mu + CONFIG.zScoreExitTarget * sigma;
let targetSpreadRateLower = mu - CONFIG.zScoreExitTarget * sigma;
let targetSpreadUpper = targetSpreadRateUpper * realtimeSpotPrice;
let targetSpreadLower = targetSpreadRateLower * realtimeSpotPrice;
let spreads = history.slice(-100).map(h => h.spread);
let avgSpread = spreads.reduce((a, b) => a + b, 0) / spreads.length;
let deviation = SignalLayer.detectPriceDeviation(pair, {zScore: realtimeZScore});
let spreadDeviation = realtimeSpread - avgSpread;
let spreadStd = sigma * realtimeSpotPrice;
let adjustmentRatio = Math.min(
Math.abs(spreadDeviation) * CONFIG.limitOrderSpreadRatio,
spreadStd * 0.5
);
let minAdjustment = realtimeSpotPrice * 0.0005;
let maxAdjustment = realtimeSpotPrice * 0.005;
adjustmentRatio = Math.max(minAdjustment, Math.min(maxAdjustment, adjustmentRatio));
let spotLimitPrice = 0;
let deliveryLimitPrice = 0;
if (direction === 'positive') {
spotLimitPrice = realtimeSpotPrice + adjustmentRatio;
deliveryLimitPrice = realtimeDeliveryPrice - adjustmentRatio;
} else {
spotLimitPrice = realtimeSpotPrice - adjustmentRatio;
deliveryLimitPrice = realtimeDeliveryPrice + adjustmentRatio;
}
Log('📝 限价单价格(基于Depth+调整):');
Log(' 现货挂单价:', spotLimitPrice.toFixed(2));
Log(' 期货挂单价:', deliveryLimitPrice.toFixed(2));
let firstIsSpot = deviation.isSpotDeviation || (!deviation.isDeliveryDeviation);
Log('🎯 开仓策略:', direction === 'positive' ? '正套(价差过大)' : '反套(价差过小)');
Log('🎯 执行顺序:', firstIsSpot ? '先开现货' : '先开期货');
Log('===================================');
let contractAmount = ExecutionLayer.calculateContractAmount(spotAmount, pair.ctVal);
try {
let spotOrder = null;
let deliveryOrder = null;
exchanges[1].SetMarginLevel(CONFIG.marginLevel);
// ==================== 正套开仓 ====================
if (direction === 'positive') {
if (firstIsSpot) {
// 先开现货买单
spotOrder = ExecutionLayer.createOrderWithFallback(
exchanges[0], pair.spotSymbol, 'buy', spotAmount, spotLimitPrice, '现货'
);
if (!spotOrder) {
Log('❌ 现货买单失败,开仓终止');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '现货买单失败');
return false;
}
DisplayLayer.printOrderDetails(exchanges[0], pair.spotSymbol, spotOrder.Id, '现货买入订单');
// 再开期货卖单
deliveryOrder = ExecutionLayer.createOrderWithFallback(
exchanges[1], pair.deliverySymbol, 'sell', contractAmount, deliveryLimitPrice, '期货'
);
if (!deliveryOrder) {
Log('❌ 期货卖单失败,需要回滚现货');
// ✅ v5.1: 使用增强的回滚验证
if (!ExecutionLayer.rollbackSpotOrder(exchanges[0], pair, 'sell', spotAmount, '期货卖单失败')) {
Log('❌ 回滚现货失败!可能留有残余仓位,请手动检查');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '期货卖单失败且回滚失败');
return false;
}
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '期货卖单失败,已成功回滚现货');
return false;
}
DisplayLayer.printOrderDetails(exchanges[1], pair.deliverySymbol, deliveryOrder.Id, '期货卖出订单');
} else {
// 先开期货卖单
deliveryOrder = ExecutionLayer.createOrderWithFallback(
exchanges[1], pair.deliverySymbol, 'sell', contractAmount, deliveryLimitPrice, '期货'
);
if (!deliveryOrder) {
Log('❌ 期货卖单失败,开仓终止');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '期货卖单失败');
return false;
}
DisplayLayer.printOrderDetails(exchanges[1], pair.deliverySymbol, deliveryOrder.Id, '期货卖出订单');
// 再开现货买单
spotOrder = ExecutionLayer.createOrderWithFallback(
exchanges[0], pair.spotSymbol, 'buy', spotAmount, spotLimitPrice, '现货'
);
if (!spotOrder) {
Log('❌ 现货买单失败,需要回滚期货');
// ✅ v5.1: 使用增强的回滚验证
if (!ExecutionLayer.rollbackDeliveryOrder(exchanges[1], pair, 'closesell', contractAmount, '现货买单失败')) {
Log('❌ 回滚期货失败!可能留有残余仓位,请手动检查');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '现货买单失败且回滚失败');
return false;
}
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '现货买单失败,已成功回滚期货');
return false;
}
DisplayLayer.printOrderDetails(exchanges[0], pair.spotSymbol, spotOrder.Id, '现货买入订单');
}
// ==================== 反套开仓 ====================
} else {
if (firstIsSpot) {
// 先开现货卖单
spotOrder = ExecutionLayer.createOrderWithFallback(
exchanges[0], pair.spotSymbol, 'sell', spotAmount, spotLimitPrice, '现货'
);
if (!spotOrder) {
Log('❌ 现货卖单失败,开仓终止');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '现货卖单失败');
return false;
}
DisplayLayer.printOrderDetails(exchanges[0], pair.spotSymbol, spotOrder.Id, '现货卖出订单');
// 再开期货买单
deliveryOrder = ExecutionLayer.createOrderWithFallback(
exchanges[1], pair.deliverySymbol, 'buy', contractAmount, deliveryLimitPrice, '期货'
);
if (!deliveryOrder) {
Log('❌ 期货买单失败,需要回滚现货');
// ✅ v5.1: 使用增强的回滚验证
if (!ExecutionLayer.rollbackSpotOrder(exchanges[0], pair, 'buy', spotAmount, '期货买单失败')) {
Log('❌ 回滚现货失败!可能留有残余仓位,请手动检查');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '期货买单失败且回滚失败');
return false;
}
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '期货买单失败,已成功回滚现货');
return false;
}
DisplayLayer.printOrderDetails(exchanges[1], pair.deliverySymbol, deliveryOrder.Id, '期货买入订单');
} else {
// 先开期货买单
deliveryOrder = ExecutionLayer.createOrderWithFallback(
exchanges[1], pair.deliverySymbol, 'buy', contractAmount, deliveryLimitPrice, '期货'
);
if (!deliveryOrder) {
Log('❌ 期货买单失败,开仓终止');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '期货买单失败');
return false;
}
DisplayLayer.printOrderDetails(exchanges[1], pair.deliverySymbol, deliveryOrder.Id, '期货买入订单');
// 再开现货卖单
spotOrder = ExecutionLayer.createOrderWithFallback(
exchanges[0], pair.spotSymbol, 'sell', spotAmount, spotLimitPrice, '现货'
);
if (!spotOrder) {
Log('❌ 现货卖单失败,需要回滚期货');
// ✅ v5.1: 使用增强的回滚验证
if (!ExecutionLayer.rollbackDeliveryOrder(exchanges[1], pair, 'closebuy', contractAmount, '现货卖单失败')) {
Log('❌ 回滚期货失败!可能留有残余仓位,请手动检查');
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '现货卖单失败且回滚失败');
return false;
}
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '现货卖单失败,已成功回滚期货');
return false;
}
DisplayLayer.printOrderDetails(exchanges[0], pair.spotSymbol, spotOrder.Id, '现货卖出订单');
}
}
let openSpotPrice = spotOrder.AvgPrice || spotLimitPrice;
let openDeliveryPrice = deliveryOrder.AvgPrice || deliveryLimitPrice;
let openSpread = openDeliveryPrice - openSpotPrice;
let openSpreadRate = openSpread / openSpotPrice;
// 动态调整目标区间
Log('-----------------------------------');
Log('🔧 根据实际成交价差动态调整目标区间:');
Log(' 初始目标区间: [', targetSpreadLower.toFixed(2), ',', targetSpreadUpper.toFixed(2), ']');
Log(' 实际成交价差:', openSpread.toFixed(2));
let minProfitDistance = CONFIG.useFixedProfitSpace
? CONFIG.minProfitSpaceUSDT
: openSpotPrice * CONFIG.minProfitSpaceRate;
Log(` 最小盈利空间要求: ${minProfitDistance.toFixed(2)} USDT`);
let adjustmentNeeded = false;
let originalTargetLower = targetSpreadLower;
let originalTargetUpper = targetSpreadUpper;
let targetRangeWidth = originalTargetUpper - originalTargetLower;
if (direction === 'positive') {
let distanceToTarget = openSpread - targetSpreadLower;
Log(' 正套策略:需要价差缩小到目标下限');
Log(` 当前距离目标下限: ${distanceToTarget.toFixed(2)} USDT`);
if (distanceToTarget < minProfitDistance) {
adjustmentNeeded = true;
let adjustment = minProfitDistance - distanceToTarget;
targetSpreadLower = openSpread - minProfitDistance;
targetSpreadUpper = targetSpreadLower + targetRangeWidth;
Log(' ⚠️ 成交价差过于接近原目标区间,需要向下调整');
Log(` 调整幅度: -${adjustment.toFixed(2)} USDT`);
} else {
Log(' ✅ 成交价差合理,保持原目标区间');
}
} else {
let distanceToTarget = targetSpreadUpper - openSpread;
Log(' 反套策略:需要价差扩大到目标上限');
Log(` 当前距离目标上限: ${distanceToTarget.toFixed(2)} USDT`);
if (distanceToTarget < minProfitDistance) {
adjustmentNeeded = true;
let adjustment = minProfitDistance - distanceToTarget;
targetSpreadUpper = openSpread + minProfitDistance;
targetSpreadLower = targetSpreadUpper - targetRangeWidth;
Log(' ⚠️ 成交价差过于接近原目标区间,需要向上调整');
Log(` 调整幅度: +${adjustment.toFixed(2)} USDT`);
} else {
Log(' ✅ 成交价差合理,保持原目标区间');
}
}
if (adjustmentNeeded) {
Log(' 调整后目标区间: [', targetSpreadLower.toFixed(2), ',', targetSpreadUpper.toFixed(2), ']');
}
Log('===================================');
Log('✅ 开仓成功!');
Log('===================================');
Log('💰 成交信息:');
Log(' 成交现货价:', openSpotPrice.toFixed(2));
Log(' 成交期货价:', openDeliveryPrice.toFixed(2));
Log(' 成交价差:', openSpread.toFixed(4), `(${(openSpreadRate*100).toFixed(3)}%)`);
Log(' 最终目标平仓区间: [', targetSpreadLower.toFixed(2), ',', targetSpreadUpper.toFixed(2), ']');
if (adjustmentNeeded) {
Log(' ✅ (已根据成交价差动态调整)');
}
Log('===================================');
positionRecords[pair.deliverySymbol] = {
coin: pair.coin,
spotSymbol: pair.spotSymbol,
deliverySymbol: pair.deliverySymbol,
deliveryType: pair.deliveryType,
deliveryTypeName: pair.deliveryTypeName,
direction: direction,
deviation: deviation,
openTime: Date.now(),
openSpotPrice: openSpotPrice,
openDeliveryPrice: openDeliveryPrice,
openSpread: openSpread,
openSpreadRate: openSpreadRate,
openZScore: realtimeZScore,
openMean: mu,
openStd: sigma,
targetSpreadUpper: targetSpreadUpper,
targetSpreadLower: targetSpreadLower,
originalTargetUpper: originalTargetUpper,
originalTargetLower: originalTargetLower,
targetAdjusted: adjustmentNeeded,
spotAmount: spotAmount,
contractAmount: contractAmount,
ctVal: pair.ctVal,
spotOrderId: spotOrder.Id,
deliveryOrderId: deliveryOrder.Id
};
_G('positionRecords', positionRecords);
return true;
} catch (e) {
Log('❌ 开仓异常:', e.message);
ExecutionLayer.addCooldown(pair.deliverySymbol, pair.coin, '开仓异常: ' + e.message);
return false;
}
},
/**
* 平仓执行(带止损二次确认)
*/
closePosition: function(deliverySymbol, currentPair, closeReason) {
Log('===================================');
Log('🚀 开始平仓流程');
Log('===================================');
Log('币种:', currentPair.coin);
Log('交割合约:', deliverySymbol);
Log('平仓原因:', closeReason.reason);
Log('详细信息:', closeReason.detail);
let record = positionRecords[deliverySymbol];
if (!record) {
Log('❌ 未找到开仓记录');
return false;
}
let snapshot = closeReason.priceSnapshot;
if (snapshot) {
Log('-----------------------------------');
Log('📸 平仓信号快照:');
Log(' 快照时间:', new Date(snapshot.snapshotTime).toLocaleString());
Log(' 现货Depth:', snapshot.snapshotSpotPrice.toFixed(2));
Log(' 期货Depth:', snapshot.snapshotDeliveryPrice.toFixed(2));
Log(' 价差:', snapshot.snapshotSpread.toFixed(2));
}
// ✅ 止损二次确认
if (closeReason.needsConfirmation && closeReason.reason.includes('止损')) {
Log('-----------------------------------');
Log('🔄 止损二次确认:重新计算实时盈亏...');
Sleep(500);
let verifyPnl = SignalLayer.calculateUnrealizedPnL(record, currentPair);
if (!verifyPnl) {
Log('❌ 无法获取实时盈亏,取消平仓');
ExecutionLayer.addOpportunityLostCooldown(deliverySymbol, '止损二次确认失败');
return false;
}
Log(' 初次判断盈亏率:', (snapshot ? '(从快照)' : ''), '需验证');
Log(' 实时验证盈亏率:', (verifyPnl.totalPnlRate * 100).toFixed(4), '%');
Log(' 止损线:', (-CONFIG.stopLossRate * 100).toFixed(2), '%');
if (verifyPnl.totalPnlRate > -CONFIG.stopLossRate) {
Log('✅ 二次确认:盈亏已回升,取消止损平仓');
Log(` 当前盈亏 ${(verifyPnl.totalPnlRate*100).toFixed(4)}% > 止损线 ${(-CONFIG.stopLossRate*100).toFixed(2)}%`);
ExecutionLayer.addOpportunityLostCooldown(deliverySymbol, '止损二次确认未通过,盈亏回升');
return false;
}
Log('❌ 二次确认:止损条件仍然满足,继续平仓');
Log(` 当前盈亏 ${(verifyPnl.totalPnlRate*100).toFixed(4)}% <= 止损线 ${(-CONFIG.stopLossRate*100).toFixed(2)}%`);
}
// 二次验证最新Depth价格
Log('-----------------------------------');
Log('🔄 二次验证:获取最新Depth价格...');
Sleep(100);
let verifySpotPrice = DataLayer.getDepthMidPrice(exchanges[0], record.spotSymbol, true);
let verifyDeliveryPrice = DataLayer.getDepthMidPrice(exchanges[1], record.deliverySymbol, true);
if (!verifySpotPrice || !verifyDeliveryPrice) {
Log('❌ 二次验证:获取Depth价格失败');
ExecutionLayer.addOpportunityLostCooldown(deliverySymbol, '平仓前二次验证Depth获取失败');
return false;
}
let verifySpread = verifyDeliveryPrice - verifySpotPrice;
Log('✅ 二次验证Depth价格:');
Log(' 验证现货价:', verifySpotPrice.toFixed(2));
Log(' 验证期货价:', verifyDeliveryPrice.toFixed(2));
Log(' 验证价差:', verifySpread.toFixed(2));
// 验证价格是否仍然有利(仅针对价差回归)
let shouldProceed = false;
if (closeReason.reason === '✅ 价差回归目标区间') {
if (verifySpread >= record.targetSpreadLower &&
verifySpread <= record.targetSpreadUpper) {
shouldProceed = true;
Log('✅ 验证通过:价差仍在目标区间');
} else {
let lowerTolerance = record.targetSpreadLower - CONFIG.spreadTolerance;
let upperTolerance = record.targetSpreadUpper + CONFIG.spreadTolerance;
if (verifySpread >= lowerTolerance && verifySpread <= upperTolerance) {
shouldProceed = true;
Log('⚠️ 验证通过(容忍范围内):价差略微偏离但可接受');
} else {
shouldProceed = false;
Log('❌ 验证失败:价差已偏离目标区间过多');
}
}
if (!shouldProceed) {
ExecutionLayer.addOpportunityLostCooldown(deliverySymbol, `价差偏离目标区间: ${verifySpread.toFixed(2)}`);
return false;
}
} else {
shouldProceed = true;
Log('✅ 非价差回归平仓,直接执行');
}
// 执行平仓
Log('-----------------------------------');
Log('📋 平仓策略:');
if (record.direction === 'positive') {
Log(' 正套平仓:先卖现货 → 后平期货空单');
} else {
Log(' 反套平仓:先平期货多单 → 后买现货');
}
try {
let spotOrderId = null;
let deliveryOrderId = null;
let spotOrder = null;
let deliveryOrder = null;
Log('-----------------------------------');
Log('⚡ 并发提交平仓订单...');
if (record.direction === 'positive') {
spotOrderId = exchanges[0].CreateOrder(record.spotSymbol, 'sell', -1, record.spotAmount);
if (!spotOrderId) {
Log('❌ 现货卖单提交失败');
return false;
}
Log(`✅ 现货卖单已提交: ${spotOrderId}`);
deliveryOrderId = exchanges[1].CreateOrder(record.deliverySymbol, 'closesell', -1, record.contractAmount);
if (!deliveryOrderId) {
Log('❌ 期货平空单提交失败');
return false;
}
Log(`✅ 期货平空单已提交: ${deliveryOrderId}`);
} else {
deliveryOrderId = exchanges[1].CreateOrder(record.deliverySymbol, 'closebuy', -1, record.contractAmount);
if (!deliveryOrderId) {
Log('❌ 期货平多单提交失败');
return false;
}
Log(`✅ 期货平多单已提交: ${deliveryOrderId}`);
let currentPrice = DataLayer.getDepthMidPrice(exchanges[0], record.spotSymbol);
if (!currentPrice) currentPrice = verifySpotPrice;
let usdtAmount = record.spotAmount * currentPrice;
spotOrderId = exchanges[0].CreateOrder(record.spotSymbol, 'buy', -1, usdtAmount);
if (!spotOrderId) {
Log('❌ 现货买单提交失败');
return false;
}
Log(`✅ 现货买单已提交: ${spotOrderId}`);
}
// 轮询检查订单状态
Log('-----------------------------------');
Log('⏱️ 轮询检查订单状态(最长15秒)...');
let startTime = Date.now();
let checkCount = 0;
let maxChecks = 15;
while (checkCount < maxChecks) {
Sleep(1000);
checkCount++;
spotOrder = exchanges[0].GetOrder(spotOrderId);
deliveryOrder = exchanges[1].GetOrder(deliveryOrderId);
let spotStatus = spotOrder ? spotOrder.Status : -1;
let deliveryStatus = deliveryOrder ? deliveryOrder.Status : -1;
Log(` 第${checkCount}次检查: 现货状态=${spotStatus}, 期货状态=${deliveryStatus}`);
if (spotStatus === 1 && deliveryStatus === 1) {
Log('✅ 两个订单都已成交!');
break;
}
}
// 处理未成交情况
let finalSpotStatus = spotOrder ? spotOrder.Status : -1;
let finalDeliveryStatus = deliveryOrder ? deliveryOrder.Status : -1;
if (finalSpotStatus === 1 && finalDeliveryStatus !== 1) {
Log('⚠️ 现货已成交,期货未成交 - 强制平仓期货');
if (finalDeliveryStatus === 0) exchanges[1].CancelOrder(deliveryOrderId);
let forceDirection = record.direction === 'positive' ? 'closesell' : 'closebuy';
let forceOrderId = exchanges[1].CreateOrder(record.deliverySymbol, forceDirection, -1, record.contractAmount);
if (forceOrderId) {
Sleep(2000);
deliveryOrder = exchanges[1].GetOrder(forceOrderId);
}
}
if (finalDeliveryStatus === 1 && finalSpotStatus !== 1) {
Log('⚠️ 期货已成交,现货未成交 - 强制成交现货');
if (finalSpotStatus === 0) exchanges[0].CancelOrder(spotOrderId);
if (record.direction === 'positive') {
spotOrderId = exchanges[0].CreateOrder(record.spotSymbol, 'sell', -1, record.spotAmount);
} else {
let currentPrice = DataLayer.getDepthMidPrice(exchanges[0], record.spotSymbol);
if (!currentPrice) currentPrice = verifySpotPrice;
let usdtAmount = record.spotAmount * currentPrice;
spotOrderId = exchanges[0].CreateOrder(record.spotSymbol, 'buy', -1, usdtAmount);
}
if (spotOrderId) {
Sleep(2000);
spotOrder = exchanges[0].GetOrder(spotOrderId);
}
}
if (finalSpotStatus !== 1 && finalDeliveryStatus !== 1) {
Log('❌ 两个订单都未成交');
ExecutionLayer.addOpportunityLostCooldown(deliverySymbol, '平仓订单未成交');
return false;
}
if (spotOrder && spotOrder.Status === 1) {
DisplayLayer.printOrderDetails(exchanges[0], record.spotSymbol, spotOrder.Id, '平仓-现货订单');
}
if (deliveryOrder && deliveryOrder.Status === 1) {
DisplayLayer.printOrderDetails(exchanges[1], record.deliverySymbol, deliveryOrder.Id, '平仓-期货订单');
}
// 成交价差验证和盈亏统计
let closeSpotPrice = spotOrder.AvgPrice || verifySpotPrice;
let closeDeliveryPrice = deliveryOrder.AvgPrice || verifyDeliveryPrice;
let actualCloseSpread = closeDeliveryPrice - closeSpotPrice;
let openSpotOrder = exchanges[0].GetOrder(record.spotOrderId);
let openDeliveryOrder = exchanges[1].GetOrder(record.deliveryOrderId);
let openSpotPrice = (openSpotOrder && openSpotOrder.AvgPrice) ? openSpotOrder.AvgPrice : record.openSpotPrice;
let openDeliveryPrice = (openDeliveryOrder && openDeliveryOrder.AvgPrice) ? openDeliveryOrder.AvgPrice : record.openDeliveryPrice;
let openSpread = openDeliveryPrice - openSpotPrice;
let spotReturnRate = 0;
let deliveryReturnRate = 0;
if (record.direction === 'positive') {
spotReturnRate = (closeSpotPrice - openSpotPrice) / openSpotPrice;
deliveryReturnRate = (openDeliveryPrice - closeDeliveryPrice) / openDeliveryPrice;
} else {
spotReturnRate = (openSpotPrice - closeSpotPrice) / openSpotPrice;
deliveryReturnRate = (closeDeliveryPrice - openDeliveryPrice) / openDeliveryPrice;
}
let totalReturnRate = spotReturnRate + deliveryReturnRate;
let requiredUSD = openSpotPrice * record.spotAmount;
let actualTotalPnl = totalReturnRate * requiredUSD;
let actualSpotPnl = spotReturnRate * requiredUSD;
let actualDeliveryPnl = deliveryReturnRate * requiredUSD;
accumulatedProfit += actualTotalPnl;
_G('accumulatedProfit', accumulatedProfit);
Log('===================================');
Log('💰 平仓盈亏统计');
Log('===================================');
Log('📊 开仓时价格:');
Log(' 现货价:', openSpotPrice.toFixed(2));
Log(' 期货价:', openDeliveryPrice.toFixed(2));
Log(' 价差:', openSpread.toFixed(4));
Log('-----------------------------------');
Log('📊 平仓时价格:');
Log(' 现货价:', closeSpotPrice.toFixed(2));
Log(' 期货价:', closeDeliveryPrice.toFixed(2));
Log(' 价差:', actualCloseSpread.toFixed(4));
Log('-----------------------------------');
Log('📈 收益分析:');
Log(' 现货收益率:', (spotReturnRate * 100).toFixed(4), '%', `| 盈亏: ${actualSpotPnl >= 0 ? '+' : ''}${actualSpotPnl.toFixed(4)} USDT`);
Log(' 合约收益率:', (deliveryReturnRate * 100).toFixed(4), '%', `| 盈亏: ${actualDeliveryPnl >= 0 ? '+' : ''}${actualDeliveryPnl.toFixed(4)} USDT`);
Log(' 总收益率:', (totalReturnRate * 100).toFixed(4), '%');
Log('-----------------------------------');
Log('💵 资金统计:');
Log(' 本次盈亏:', actualTotalPnl >= 0 ? `+${actualTotalPnl.toFixed(4)}` : actualTotalPnl.toFixed(4), 'USDT');
Log(' 累计盈亏:', accumulatedProfit >= 0 ? `+${accumulatedProfit.toFixed(4)}` : accumulatedProfit.toFixed(4), 'USDT');
Log('===================================');
closedTrades.push({
coin: record.coin,
deliverySymbol: deliverySymbol,
direction: record.direction,
deviation: record.deviation,
openTime: record.openTime,
closeTime: Date.now(),
openSpotPrice: openSpotPrice,
openDeliveryPrice: openDeliveryPrice,
closeSpotPrice: closeSpotPrice,
closeDeliveryPrice: closeDeliveryPrice,
openSpread: openSpread,
closeSpread: actualCloseSpread,
targetSpreadLower: record.targetSpreadLower,
targetSpreadUpper: record.targetSpreadUpper,
targetAdjusted: record.targetAdjusted || false,
spotAmount: record.spotAmount,
contractAmount: record.contractAmount,
spotReturnRate: spotReturnRate,
deliveryReturnRate: deliveryReturnRate,
totalReturnRate: totalReturnRate,
actualPnl: actualTotalPnl,
closeReason: closeReason.reason
});
_G('closedTrades', closedTrades);
delete positionRecords[deliverySymbol];
_G('positionRecords', positionRecords);
DisplayLayer.updateProfit();
Log('✅ 平仓完成!');
Log('===================================');
return true;
} catch (e) {
Log('❌ 平仓异常:', e.message);
return false;
}
}
};
// ==================== 展示层 (DisplayLayer) ====================
const DisplayLayer = {
/**
* 打印订单详细信息
*/
printOrderDetails: function(exchange, symbol, orderId, orderType) {
Log('================================');
Log(`📋 查询订单详情: ${orderType}`);
Log(` 交易对: ${symbol}`);
Log(` 订单ID: ${orderId}`);
Sleep(1000);
let order = exchange.GetOrder(orderId);
if (!order) {
Log('❌ 无法获取订单详情');
return null;
}
let statusText = '';
switch(order.Status) {
case 0: statusText = '等待成交'; break;
case 1: statusText = '已成交'; break;
case 2: statusText = '已撤销'; break;
case 3: statusText = '未知'; break;
default: statusText = `未定义状态(${order.Status})`;
}
Log('✅ 订单详情:');
Log(` 订单ID: ${order.Id}`);
Log(` 交易对: ${order.Symbol || symbol}`);
Log(` 订单类型: ${order.Type === 0 ? '买入' : order.Type === 1 ? '卖出' : '未知'}`);
Log(` 订单价格: ${order.Price ? order.Price.toFixed(6) : '市价'}`);
Log(` 平均成交价(AvgPrice): ${order.AvgPrice ? order.AvgPrice.toFixed(6) : 'N/A'} 💰`);
Log(` 订单数量: ${order.Amount ? order.Amount.toFixed(6) : 'N/A'}`);
Log(` 成交数量(DealAmount): ${order.DealAmount ? order.DealAmount.toFixed(6) : 'N/A'}`);
Log(` 订单状态: ${statusText}`);
Log('================================');
return order;
},
/**
* 更新收益显示
*/
updateProfit: function() {
try {
LogProfit(accumulatedProfit, "&");
} catch (e) {
Log('❌ 更新收益异常:', e.message);
}
},
/**
* 创建系统状态表格
*/
createSystemStatusTable: function(loopCount) {
let realizedProfit = 0;
let winCount = 0;
closedTrades.forEach(t => {
realizedProfit += t.actualPnl;
if (t.actualPnl > 0) winCount++;
});
let winRate = closedTrades.length > 0 ? (winCount / closedTrades.length * 100) : 0;
let floatingProfit = 0;
let activePairsCount = Object.keys(positionRecords).length;
let totalPairsCount = contractPairs.length;
for (let symbol in positionRecords) {
let record = positionRecords[symbol];
let pair = contractPairs.find(p => p.deliverySymbol === symbol);
if (record && pair) {
let pnlInfo = SignalLayer.calculateUnrealizedPnL(record, pair);
if (pnlInfo) floatingProfit += pnlInfo.totalPnl;
}
}
let runningTimeStr = "-";
if (strategyStartTime > 0) {
let diff = Date.now() - strategyStartTime;
let days = Math.floor(diff / (1000 * 60 * 60 * 24));
let hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
let mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
runningTimeStr = `${days}d ${hours}h ${mins}m`;
}
let nextCheckMinutes = Math.floor((nextStationarityCheckTime - Date.now()) / 60000);
let adfStatus = nextCheckMinutes <= 0 ? "⚠️ 检验中" : `⏳ ${nextCheckMinutes}分钟后`;
const table = {
type: "table",
title: `📊 策略看板 v5.1 (增强回滚验证) | 运行: ${runningTimeStr} | 循环: ${loopCount}`,
cols: ["指标", "数值"],
rows: []
};
table.rows.push(["💰 累计盈亏", `${accumulatedProfit >= 0 ? '+' : ''}${accumulatedProfit.toFixed(2)} USDT`]);
table.rows.push(["📈 已实现盈亏", `${realizedProfit >= 0 ? '+' : ''}${realizedProfit.toFixed(2)} USDT`]);
table.rows.push(["⚖️ 未实现盈亏", `${floatingProfit >= 0 ? '+' : ''}${floatingProfit.toFixed(2)} USDT`]);
table.rows.push(["📜 总交易笔数", `${closedTrades.length} 笔`]);
table.rows.push(["🎯 交易胜率", winRate > 0 ? `${winRate.toFixed(1)}%` : "-"]);
table.rows.push(["💼 持仓数/总对", `${activePairsCount} / ${totalPairsCount}`]);
table.rows.push(["🔬 ADF检验", adfStatus]);
return table;
},
/**
* 创建持仓表格
*/
createPositionTable: function() {
const positionTable = {
type: "table",
title: "💼 当前持仓状态 (基于Depth实时价格)",
cols: ["币种", "类型", "方向", "数量", "开仓价", "当前Depth价", "收益率", "实时盈亏", "持仓时长", "平仓条件"],
rows: []
};
if (Object.keys(positionRecords).length === 0) {
positionTable.rows.push([
"📭", "空仓", "-", "-", "-", "-", "-", "-", "-", "等待开仓信号"
]);
return positionTable;
}
for (let deliverySymbol in positionRecords) {
let record = positionRecords[deliverySymbol];
let coin = record.coin;
let currentPair = contractPairs.find(p => p.deliverySymbol === deliverySymbol);
if (!currentPair) continue;
let deliveryPos = ExecutionLayer.getPositionBySymbol(deliverySymbol);
if (!deliveryPos) continue;
let holdHours = ((Date.now() - record.openTime) / (1000 * 60 * 60)).toFixed(1);
let pnl = SignalLayer.calculateUnrealizedPnL(record, currentPair);
let returnRateDisplay = "⏳";
let pnlDisplay = "⏳";
let closeConditionDisplay = "⏳";
let currentPriceDisplay = "⏳";
if (pnl) {
currentPriceDisplay = `$${pnl.currentSpotPrice.toFixed(2)}`;
if (pnl.totalPnlRate > 0) {
returnRateDisplay = `🟢 +${(pnl.totalPnlRate * 100).toFixed(3)}%`;
pnlDisplay = `🟢 +$${pnl.totalPnl.toFixed(2)}`;
} else if (pnl.totalPnlRate < 0) {
returnRateDisplay = `🔴 ${(pnl.totalPnlRate * 100).toFixed(3)}%`;
pnlDisplay = `🔴 $${pnl.totalPnl.toFixed(2)}`;
} else {
returnRateDisplay = `⚪ 0%`;
pnlDisplay = `⚪ $0`;
}
let closeCheck = SignalLayer.shouldClosePosition(deliverySymbol, currentPair);
if (closeCheck.should) {
closeConditionDisplay = `🚨 ${closeCheck.reason}`;
} else {
let currentSpread = pnl.currentDeliveryPrice - pnl.currentSpotPrice;
if (currentSpread >= record.targetSpreadLower && currentSpread <= record.targetSpreadUpper) {
closeConditionDisplay = `✅ 已进入目标区间`;
} else {
closeConditionDisplay = `📊 监控中`;
}
}
}
let spotDirection = record.direction === 'positive' ? "📈 多" : "📉 空";
positionTable.rows.push([
`💎 ${coin}`,
"现货",
spotDirection,
record.spotAmount.toFixed(4),
`$${record.openSpotPrice.toFixed(2)}`,
currentPriceDisplay,
returnRateDisplay,
pnlDisplay,
`${holdHours}h`,
closeConditionDisplay
]);
let deliveryIsLong = deliveryPos.Type === PD_LONG || deliveryPos.Type === 0;
let deliveryDirection = deliveryIsLong ? "📈 多" : "📉 空";
let deliveryPriceDisplay = pnl ? `$${pnl.currentDeliveryPrice.toFixed(2)}` : "⏳";
let targetDisplay = `目标: [${record.targetSpreadLower.toFixed(1)}, ${record.targetSpreadUpper.toFixed(1)}]`;
if (record.targetAdjusted) {
targetDisplay += ' 🔧';
}
positionTable.rows.push([
"",
`交割(×${record.ctVal})`,
deliveryDirection,
Math.abs(deliveryPos.Amount).toFixed(4),
`$${record.openDeliveryPrice.toFixed(2)}`,
deliveryPriceDisplay,
"-",
"-",
"-",
targetDisplay
]);
positionTable.rows.push([
"---", "---", "---", "---", "---", "---", "---", "---", "---", "---"
]);
}
if (positionTable.rows.length > 0 && positionTable.rows[positionTable.rows.length - 1][0] === "---") {
positionTable.rows.pop();
}
return positionTable;
},
/**
* 创建平稳性表格
*/
createStationarityTable: function() {
const table = {
type: "table",
title: "🔬 ADF平稳性检验(每小时)",
cols: ["币种", "交割", "数据量", "ADF p值", "平稳", "失败次数", "可交易", "Z-Score", "信号", "状态"],
rows: []
};
let validPairs = contractPairs.filter(p => p.daysToDelivery >= CONFIG.minDaysToDelivery);
for (let pair of validPairs) {
let stationarity = stationarityCache[pair.deliverySymbol];
let zInfo = SignalLayer.calculateZScore(pair.deliverySymbol);
let dataLengthDisplay = stationarity && stationarity.dataLength ?
`${stationarity.dataLength}` : "-";
let adfDisplay = stationarity && stationarity.adf ?
stationarity.adf.pValue.toFixed(4) : "-";
let stationaryDisplay = "";
if (!stationarity) {
stationaryDisplay = "⏳";
} else if (stationarity.isStationary) {
stationaryDisplay = "✅";
} else {
stationaryDisplay = "❌";
}
let failsDisplay = stationarity && stationarity.consecutiveFails !== undefined ?
`${stationarity.consecutiveFails}/${CONFIG.consecutiveFailThreshold}` : "-";
let canTradeDisplay = "";
if (!stationarity) {
canTradeDisplay = "⏳";
} else if (stationarity.canTrade) {
canTradeDisplay = "✅ 是";
} else {
canTradeDisplay = "❌ 否";
}
let zDisplay = "-";
let signalDisplay = "⏳";
if (stationarity && stationarity.dataLength >= CONFIG.minHistoryForTest) {
if (zInfo) {
zDisplay = zInfo.zScore.toFixed(2);
let absZ = Math.abs(zInfo.zScore);
let hasPosition = positionRecords.hasOwnProperty(pair.deliverySymbol);
if (hasPosition) {
signalDisplay = "💼 持仓";
} else if (stationarity.canTrade && absZ >= CONFIG.zScoreEntry) {
signalDisplay = "🚀 开仓";
} else if (stationarity.canTrade) {
signalDisplay = "👀 监控";
} else {
signalDisplay = "⚠️ 禁止";
}
}
}
let statusDisplay = "✅";
if (opportunityLostCooldowns[pair.deliverySymbol]) {
let cooldownEnd = opportunityLostCooldowns[pair.deliverySymbol];
let now = Date.now();
if (now < cooldownEnd) {
let remainingSeconds = Math.ceil((cooldownEnd - now) / 1000);
statusDisplay = `⏸️ ${remainingSeconds}秒`;
signalDisplay = "⏸️ 1分冷却";
}
}
if (pairCooldowns[pair.deliverySymbol]) {
let cooldownEnd = pairCooldowns[pair.deliverySymbol];
let now = Date.now();
if (now < cooldownEnd) {
let remainingMinutes = Math.ceil((cooldownEnd - now) / 60000);
statusDisplay = `⏸️ ${remainingMinutes}分`;
signalDisplay = "⏸️ 10分冷却";
}
}
table.rows.push([
`💎 ${pair.coin}`,
pair.deliveryTypeName,
dataLengthDisplay,
adfDisplay,
stationaryDisplay,
failsDisplay,
canTradeDisplay,
zDisplay,
signalDisplay,
statusDisplay
]);
}
return table;
},
/**
* 创建历史交易表格
*/
createClosedTradesTable: function() {
const table = {
type: "table",
title: "📜 历史交易(最近10笔)",
cols: ["币种", "方向", "偏离方", "开仓时间", "持仓", "现货%", "合约%", "总收益%", "盈亏", "原因"],
rows: []
};
if (closedTrades.length === 0) {
table.rows.push([
"📭", "暂无历史交易", "-", "-", "-", "-", "-", "-", "-", "-"
]);
return table;
}
let recentTrades = closedTrades.slice(-10).reverse();
for (let trade of recentTrades) {
let openDate = new Date(trade.openTime);
let holdTime = ((trade.closeTime - trade.openTime) / (1000 * 60 * 60)).toFixed(1);
let directionDisplay = trade.direction === 'positive' ? '正套' : '反套';
if (trade.targetAdjusted) {
directionDisplay += ' 🔧';
}
let deviationDisplay = "-";
if (trade.deviation) {
if (trade.deviation.isSpotDeviation) {
deviationDisplay = "现货";
} else if (trade.deviation.isDeliveryDeviation) {
deviationDisplay = "期货";
} else {
deviationDisplay = "双方";
}
}
let spotRateDisplay = trade.spotReturnRate ?
`${(trade.spotReturnRate * 100).toFixed(3)}%` : '-';
let deliveryRateDisplay = trade.deliveryReturnRate ?
`${(trade.deliveryReturnRate * 100).toFixed(3)}%` : '-';
let totalRateDisplay = '';
if (trade.totalReturnRate > 0) {
totalRateDisplay = `🟢 +${(trade.totalReturnRate * 100).toFixed(3)}%`;
} else if (trade.totalReturnRate < 0) {
totalRateDisplay = `🔴 ${(trade.totalReturnRate * 100).toFixed(3)}%`;
} else {
totalRateDisplay = `⚪ 0%`;
}
let pnlDisplay = '';
if (trade.actualPnl > 0) {
pnlDisplay = `🟢 +$${trade.actualPnl.toFixed(2)}`;
} else if (trade.actualPnl < 0) {
pnlDisplay = `🔴 $${trade.actualPnl.toFixed(2)}`;
} else {
pnlDisplay = `⚪ $0`;
}
table.rows.push([
`${trade.coin}`,
directionDisplay,
deviationDisplay,
openDate.toLocaleString('zh-CN', {month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit'}),
`${holdTime}h`,
spotRateDisplay,
deliveryRateDisplay,
totalRateDisplay,
pnlDisplay,
trade.closeReason
]);
}
return table;
},
/**
* 显示完整看板
*/
displayDashboard: function(loopCount) {
const systemStatusTable = DisplayLayer.createSystemStatusTable(loopCount);
const stationarityTable = DisplayLayer.createStationarityTable();
const positionTable = DisplayLayer.createPositionTable();
const closedTradesTable = DisplayLayer.createClosedTradesTable();
const dashboardDisplay =
'`' + JSON.stringify(systemStatusTable) + '`\n\n' +
'`' + JSON.stringify(stationarityTable) + '`\n\n' +
'`' + JSON.stringify(positionTable) + '`\n\n' +
'`' + JSON.stringify(closedTradesTable) + '`';
LogStatus(dashboardDisplay);
}
};
// ==================== 主循环 ====================
function main() {
LogProfitReset();
LogReset();
Log('🚀 启动现货-交割套利策略(重构版 v5.1)');
Log('=== v5.1新增 ===');
Log(' ✅ 回滚时使用Depth价格验证,确保回滚成功');
Log(' ✅ 最多重试3次,完整的回滚验证机制');
Log('=== 核心优化 ===');
Log(' ✅ 去掉止盈,只保留止损(止损有二次确认)');
Log(' ✅ 开仓前检查现货U余额(买入)和币余额(卖出)');
Log(' ✅ 四层架构设计:数据层、信号层、执行层、展示层');
Log('=== 保持功能 ===');
Log(' ✅ 完全使用Depth盘口价格');
Log(' ✅ 平仓前二次验证');
Log(' ✅ 并发提交订单');
Log(' ✅ 根据实际成交价差动态调整目标区间');
Log(' ✅ 冷却期机制(10分钟+1分钟)');
Log('=== 交易信号 ===');
Log(' 入场: |Z-Score| >=', CONFIG.zScoreEntry);
Log(' 出场: 价差回归目标区间');
Log(' 止损:', (CONFIG.stopLossRate * 100).toFixed(1), '%(有二次确认)');
let savedRecords = _G('positionRecords');
if (savedRecords) {
positionRecords = savedRecords;
Log('📥 恢复持仓记录:', Object.keys(positionRecords).length, '个');
}
DataLayer.loadHistoryData();
if (!DataLayer.initContractPairs()) {
Log('❌ 初始化合约配对失败');
return;
}
if (!DataLayer.updatePrices()) {
Log('❌ 初始化价格失败');
return;
}
if (!DataLayer.initializeAccountRecords()) {
Log('❌ 初始化账户记录失败');
return;
}
strategyStartTime = Date.now();
nextStationarityCheckTime = Date.now();
let loopCount = 0;
let lastSaveTime = Date.now();
while (true) {
loopCount++;
if (!DataLayer.updatePrices()) {
Log('⚠️ 更新价格失败');
Sleep(CONFIG.checkInterval);
continue;
}
SignalLayer.runStationarityChecks();
// 检查现有持仓
for (let deliverySymbol in positionRecords) {
let record = positionRecords[deliverySymbol];
let currentPair = contractPairs.find(p => p.deliverySymbol === deliverySymbol);
if (!currentPair) continue;
let deliveryPos = ExecutionLayer.getPositionBySymbol(deliverySymbol);
if (!deliveryPos) {
Log(`⚠️ ${deliverySymbol} 持仓不完整,清理记录`);
delete positionRecords[deliverySymbol];
_G('positionRecords', positionRecords);
continue;
}
let closeCheck = SignalLayer.shouldClosePosition(deliverySymbol, currentPair);
if (closeCheck.should) {
ExecutionLayer.closePosition(deliverySymbol, currentPair, closeCheck);
}
}
// 寻找开仓机会
for (let pair of contractPairs) {
if (pair.daysToDelivery < CONFIG.minDaysToDelivery) continue;
if (ExecutionLayer.checkCooldown(pair.deliverySymbol, pair.coin)) continue;
if (ExecutionLayer.checkOpportunityLostCooldown(pair.deliverySymbol)) continue;
let hasPosition = positionRecords.hasOwnProperty(pair.deliverySymbol);
if (hasPosition) continue;
let openCheck = SignalLayer.shouldOpenPosition(pair);
if (openCheck.should) {
ExecutionLayer.openPosition(pair, openCheck);
}
}
if (loopCount % 10 === 0) {
DisplayLayer.updateProfit();
DisplayLayer.displayDashboard(loopCount);
}
if (Date.now() - lastSaveTime > 3600000) {
DataLayer.saveHistoryData();
lastSaveTime = Date.now();
}
Sleep(CONFIG.checkInterval);
}
}
function onexit(){
Log('清理缓存数据');
DataLayer.saveHistoryData();
Log(_G(null));
}