策略源码
/*backtest
start: 2025-10-12 00:00:00
end: 2026-01-07 00:00:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_OKX","currency":"ETH_USDT","balance":500000}]
*/
let config = {
btcSymbol: 'BTC_USDT.swap',
ethSymbol: 'ETH_USDT.swap',
triggerThreshold: 0.02,
takeProfitPct: 0.03,
stopLossPct: -0.01,
btcCoinAmount: 0.1, // BTC下单币数(基准)
betaLookback: 30, // Beta计算回溯期(天)
minBeta: 0.5, // Beta下限
maxBeta: 2.0 // Beta上限
}
let currentPosition = null
// 统计数据
let statistics = {
initialBalance: 0,
realizedProfit: 0, // ✅ 已实现盈亏
takeProfitCount: 0,
stopLossCount: 0,
winCount: 0,
lossCount: 0,
profitLossRatio: 0
}
// Beta信息
let betaInfo = {
currentBeta: 1.0,
correlation: 0,
lookbackDays: config.betaLookback,
lastUpdate: null
}
// 合约信息缓存
let contractInfo = {
btcCtVal: null,
ethCtVal: null
}
// 获取合约面值
function getContractValue() {
try {
let markets = exchange.GetMarkets()
if (!markets) {
Log("❌ 获取市场信息失败")
return false
}
if (markets[config.btcSymbol]) {
contractInfo.btcCtVal = markets[config.btcSymbol].CtVal
}
if (markets[config.ethSymbol]) {
contractInfo.ethCtVal = markets[config.ethSymbol].CtVal
}
if (!contractInfo.btcCtVal || !contractInfo.ethCtVal) {
Log("❌ 无法获取合约面值信息")
return false
}
Log("📋 合约信息 | BTC CtVal:", contractInfo.btcCtVal,
"| ETH CtVal:", contractInfo.ethCtVal)
return true
} catch (e) {
Log("❌ 获取合约面值异常:", e)
return false
}
}
// 更新状态表格
function updateStatusTable(btcTicker, ethTicker) {
// 计算当前权益
let currentAccount = _C(exchange.GetAccount)
let currentEquity = currentAccount.Equity
// 未实现盈亏
let unrealizedPnl = 0
if (currentPosition && btcTicker && ethTicker) {
let result = checkClose(currentPosition, btcTicker, ethTicker)
unrealizedPnl = result.pnlUsd
}
// 总盈亏 = 当前权益 - 初始权益
let totalProfit = currentEquity - statistics.initialBalance
let totalProfitRate = totalProfit / statistics.initialBalance
LogProfit(totalProfit, "&")
let table1 = {
type: 'table',
title: '📊 权益统计',
cols: ['初始金额', '当前权益', '已实现盈亏', '未实现盈亏', '总盈亏', '盈利率', '止盈次数', '止损次数', '胜率'],
rows: [[
'$' + _N(statistics.initialBalance, 2),
'$' + _N(currentEquity, 2),
'$' + _N(statistics.realizedProfit, 2),
(unrealizedPnl >= 0 ? '+' : '') + '$' + _N(unrealizedPnl, 2),
(totalProfit >= 0 ? '+' : '') + '$' + _N(totalProfit, 2),
_N(totalProfitRate * 100, 2) + '%',
statistics.takeProfitCount,
statistics.stopLossCount,
statistics.profitLossRatio > 0 ? _N(statistics.profitLossRatio, 2) : '-'
]]
}
let table2 = {
type: 'table',
title: '🎯 Beta对冲信息',
cols: ['历史均价比', '当前价格比', '价格比偏离', '收益率Beta', '最终Beta', '相关系数', '回溯天数', '更新时间'],
rows: [[
betaInfo.avgPriceRatio ? _N(betaInfo.avgPriceRatio, 2) : '-',
betaInfo.currentPriceRatio ? _N(betaInfo.currentPriceRatio, 2) : '-',
betaInfo.avgPriceRatio && betaInfo.currentPriceRatio ?
_N((betaInfo.currentPriceRatio - betaInfo.avgPriceRatio) / betaInfo.avgPriceRatio * 100, 2) + '%' : '-',
betaInfo.returnBeta ? _N(betaInfo.returnBeta, 3) : '-',
_N(betaInfo.currentBeta, 2),
_N(betaInfo.correlation, 3),
betaInfo.lookbackDays + '天',
betaInfo.lastUpdate ? betaInfo.lastUpdate : '-'
]]
}
let table3 = {
type: 'table',
title: '💼 实时持仓信息',
cols: ['持仓状态', 'BTC仓位', 'BTC盈亏', 'ETH仓位', 'ETH盈亏', '持仓Beta', '总浮盈', '盈亏率'],
rows: []
}
if (currentPosition && btcTicker && ethTicker) {
// 按币数计算盈亏
let btcPnlUsd, ethPnlUsd
if (currentPosition.type === 'long_btc_short_eth') {
btcPnlUsd = (btcTicker.Last - currentPosition.btcPrice) * currentPosition.btcCoinAmount
ethPnlUsd = (currentPosition.ethPrice - ethTicker.Last) * currentPosition.ethCoinAmount
} else {
btcPnlUsd = (currentPosition.btcPrice - btcTicker.Last) * currentPosition.btcCoinAmount
ethPnlUsd = (ethTicker.Last - currentPosition.ethPrice) * currentPosition.ethCoinAmount
}
let totalPnlUsd = btcPnlUsd + ethPnlUsd
let totalCost = currentPosition.btcPrice * currentPosition.btcCoinAmount +
currentPosition.ethPrice * currentPosition.ethCoinAmount
let totalPnlPct = totalPnlUsd / totalCost
let positionType = currentPosition.type === 'long_btc_short_eth' ?
'🟢 多BTC空ETH' : '🔴 空BTC多ETH'
let btcPosition = currentPosition.type === 'long_btc_short_eth' ?
'多 ' + currentPosition.btcContracts + '张(' + _N(currentPosition.btcCoinAmount, 4) + ' BTC)' :
'空 ' + currentPosition.btcContracts + '张(' + _N(currentPosition.btcCoinAmount, 4) + ' BTC)'
let ethPosition = currentPosition.type === 'long_btc_short_eth' ?
'空 ' + currentPosition.ethContracts + '张(' + _N(currentPosition.ethCoinAmount, 4) + ' ETH)' :
'多 ' + currentPosition.ethContracts + '张(' + _N(currentPosition.ethCoinAmount, 4) + ' ETH)'
table3.rows.push([
positionType,
btcPosition + ' @$' + _N(currentPosition.btcPrice, 2),
(btcPnlUsd >= 0 ? '+' : '') + '$' + _N(btcPnlUsd, 2),
ethPosition + ' @$' + _N(currentPosition.ethPrice, 2),
(ethPnlUsd >= 0 ? '+' : '') + '$' + _N(ethPnlUsd, 2),
_N(currentPosition.beta, 3),
(totalPnlUsd >= 0 ? '+' : '') + '$' + _N(totalPnlUsd, 2),
(totalPnlPct >= 0 ? '+' : '') + _N(totalPnlPct * 100, 2) + '%'
])
} else {
table3.rows.push([
'⚪ 空仓', '-', '-', '-', '-', '-', '-', '-'
])
}
LogStatus('`' + JSON.stringify([table1, table2, table3]) + '`')
}
// 计算ETH相对BTC的Beta系数
function calculateBeta(btcRecords, ethRecords, lookback) {
if (btcRecords.length < lookback + 1 || ethRecords.length < lookback + 1) {
Log("⚠️ K线数据不足,使用当前价格比作为默认Beta")
let btcPrice = btcRecords[btcRecords.length - 1].Close
let ethPrice = ethRecords[ethRecords.length - 1].Close
let defaultBeta = btcPrice / ethPrice
betaInfo.currentBeta = defaultBeta
betaInfo.correlation = 0
betaInfo.priceRatio = defaultBeta
betaInfo.returnBeta = 1.0
betaInfo.lastUpdate = new Date().toLocaleString()
Log(" 默认Beta =", _N(defaultBeta, 2), "| 价格比:", _N(btcPrice, 0), "/", _N(ethPrice, 0))
return defaultBeta
}
let btcReturns = []
let ethReturns = []
let priceRatios = []
// 计算日收益率 + 历史价格比
for (let i = btcRecords.length - lookback; i < btcRecords.length; i++) {
let btcRet = (btcRecords[i].Close - btcRecords[i-1].Close) / btcRecords[i-1].Close
let ethRet = (ethRecords[i].Close - ethRecords[i-1].Close) / ethRecords[i-1].Close
btcReturns.push(btcRet)
ethReturns.push(ethRet)
let ratio = btcRecords[i].Close / ethRecords[i].Close
priceRatios.push(ratio)
}
// 计算历史平均价格比
let avgPriceRatio = priceRatios.reduce((a, b) => a + b, 0) / priceRatios.length
// 计算价格比的标准差
let priceRatioVariance = 0
for (let i = 0; i < priceRatios.length; i++) {
let diff = priceRatios[i] - avgPriceRatio
priceRatioVariance += diff * diff
}
priceRatioVariance /= (priceRatios.length - 1)
let priceRatioStd = Math.sqrt(priceRatioVariance)
let priceRatioCv = priceRatioStd / avgPriceRatio
// 计算收益率的均值
let btcMean = btcReturns.reduce((a,b) => a+b, 0) / btcReturns.length
let ethMean = ethReturns.reduce((a,b) => a+b, 0) / ethReturns.length
// 计算协方差和方差
let covariance = 0
let btcVariance = 0
let ethVariance = 0
for (let i = 0; i < btcReturns.length; i++) {
let btcDiff = btcReturns[i] - btcMean
let ethDiff = ethReturns[i] - ethMean
covariance += btcDiff * ethDiff
btcVariance += btcDiff * btcDiff
ethVariance += ethDiff * ethDiff
}
covariance /= (btcReturns.length - 1)
btcVariance /= (btcReturns.length - 1)
ethVariance /= (ethReturns.length - 1)
// 收益率Beta
let returnBeta = covariance / btcVariance
// 计算相关系数
let correlation = covariance / Math.sqrt(btcVariance * ethVariance)
// 最终Beta = 历史平均价格比 × 收益率Beta
let finalBeta = avgPriceRatio * returnBeta
// 限制Beta范围
let minBeta = avgPriceRatio * 0.5
let maxBeta = avgPriceRatio * 2.0
finalBeta = Math.max(minBeta, Math.min(maxBeta, finalBeta))
// 获取当前价格比
let currentBtcPrice = btcRecords[btcRecords.length - 1].Close
let currentEthPrice = ethRecords[ethRecords.length - 1].Close
let currentPriceRatio = currentBtcPrice / currentEthPrice
// 更新Beta信息
betaInfo.currentBeta = finalBeta
betaInfo.correlation = correlation
betaInfo.returnBeta = returnBeta
betaInfo.avgPriceRatio = avgPriceRatio
betaInfo.currentPriceRatio = currentPriceRatio
betaInfo.priceRatioStd = priceRatioStd
betaInfo.priceRatioCv = priceRatioCv
betaInfo.lastUpdate = new Date().toLocaleString()
return finalBeta
}
// 计算对冲张数
function calculateHedgeAmount(beta) {
let btcCoinAmount = config.btcCoinAmount
let ethCoinAmount = btcCoinAmount * beta
let btcContracts = Math.floor(btcCoinAmount / contractInfo.btcCtVal)
btcContracts = Math.max(1, btcContracts)
let ethContracts = Math.floor(ethCoinAmount / contractInfo.ethCtVal)
ethContracts = Math.max(1, ethContracts)
let actualBtcCoins = btcContracts * contractInfo.btcCtVal
let actualEthCoins = ethContracts * contractInfo.ethCtVal
Log("🎯 对冲计算 | Beta:", _N(beta, 3),
"\n BTC: ", _N(actualBtcCoins, 4), "币 =", btcContracts, "张 (CtVal:", contractInfo.btcCtVal, ")",
"\n ETH: ", _N(actualEthCoins, 4), "币 =", ethContracts, "张 (CtVal:", contractInfo.ethCtVal, ")",
"\n 实际比例:", _N(actualEthCoins / actualBtcCoins, 3))
return {
btc: btcContracts,
eth: ethContracts,
btcCoins: actualBtcCoins,
ethCoins: actualEthCoins,
beta: beta
}
}
// 检查实际持仓数量
function checkActualPosition(symbol, expectedDirection) {
try {
let positions = exchange.GetPositions(symbol)
if (!positions || positions.length === 0) {
return {amount: 0, price: 0}
}
for (let pos of positions) {
if (pos.Symbol === symbol) {
if (expectedDirection === "buy" && pos.Type === 0) {
// 多仓
return {amount: pos.Amount, price: pos.Price}
} else if (expectedDirection === "sell" && pos.Type === 1) {
// 空仓
return {amount: pos.Amount, price: pos.Price}
}
}
}
return {amount: 0, price: 0}
} catch (e) {
Log("❌ 检查持仓异常:", symbol, e)
return {amount: 0, price: 0}
}
}
// 优化的市价下单函数 - 30秒内轮询订单状态
function createMarketOrder(symbol, direction, amount) {
try {
let orderId = exchange.CreateOrder(symbol, direction, -1, amount)
if (!orderId) {
Log("❌ 下单失败:", symbol, direction)
return null
}
Log("📝 订单已提交:", symbol, direction, amount, "张 | ID:", orderId)
// 30秒内轮询订单状态
let startTime = Date.now()
let maxWaitTime = 30000 // 30秒
let orderInfo = null
let checkCount = 0
while (Date.now() - startTime < maxWaitTime) {
Sleep(500)
checkCount++
orderInfo = exchange.GetOrder(orderId)
if (!orderInfo) {
Log("⚠️ 获取订单状态失败,继续轮询...")
continue
}
if (orderInfo.Status === 1) {
// 完全成交
Log("✅", symbol, direction, amount, "张 成交 @", orderInfo.AvgPrice)
return {
id: orderId,
avgPrice: orderInfo.AvgPrice,
amount: orderInfo.DealAmount,
status: orderInfo.Status
}
} else if (orderInfo.Status === 2) {
// 已取消
Log("⚠️ 订单已取消:", symbol, direction)
return null
}
// Status === 0,继续等待
if (checkCount % 10 === 0) { // 每5秒打印一次
Log("⏳ 订单未成交,继续等待... 已等待", _N((Date.now() - startTime)/1000, 1), "秒")
}
}
// 30秒超时,取消订单
Log("⏰ 订单超时30秒未成交,准备取消:", symbol, direction, "订单ID:", orderId)
let cancelResult = exchange.CancelOrder(orderId)
if (!cancelResult) {
Log("⚠️ 取消订单失败,可能已成交")
} else {
Log("✅ 订单已取消")
}
// 取消后再次检查订单状态(可能在取消时已成交)
Sleep(500)
orderInfo = exchange.GetOrder(orderId)
if (orderInfo && orderInfo.Status === 1) {
Log("✅ 订单实际已成交:", symbol, direction, orderInfo.DealAmount, "张 @", orderInfo.AvgPrice)
return {
id: orderId,
avgPrice: orderInfo.AvgPrice,
amount: orderInfo.DealAmount,
status: orderInfo.Status
}
}
// ========== 关键:检查实际持仓 ==========
Log("🔍 订单状态不明确,检查实际持仓...")
let posInfo = checkActualPosition(symbol, direction)
if (posInfo.amount > 0) {
Log("⚠️ 发现实际持仓:", symbol, direction, posInfo.amount, "张 @", posInfo.price)
return {
id: orderId,
avgPrice: posInfo.price,
amount: posInfo.amount,
status: 1 // 标记为已成交
}
}
Log("❌ 订单最终未成交,且无实际持仓:", symbol, direction)
return null
} catch (e) {
Log("❌ 下单异常:", symbol, direction, e)
return null
}
}
// 平掉意外持仓
function closeUnexpectedPosition(symbol, direction, amount) {
try {
let closeDirection = direction === "buy" ? "closebuy" : "closesell"
Log("🔄 平掉意外持仓:", symbol, closeDirection, amount, "张")
let closeOrder = createMarketOrder(symbol, closeDirection, amount)
return closeOrder !== null
} catch (e) {
Log("❌ 平仓失败:", symbol, e)
return false
}
}
// 做多BTC + 做空ETH(优化版)
function openLongBtcShortEth(btcTicker, ethTicker, btcChange, beta) {
let amounts = calculateHedgeAmount(beta)
// ========== 第一步:开BTC多仓 ==========
Log("📍 第1步:开BTC多仓", amounts.btc, "张")
let btcOrder = createMarketOrder(config.btcSymbol, "buy", amounts.btc)
// 检查BTC实际持仓(无论订单是否成功都要检查)
let btcPosInfo = checkActualPosition(config.btcSymbol, "buy")
if (btcPosInfo.amount === 0) {
Log("❌ BTC未开仓成功,终止流程")
return null
}
if (btcPosInfo.amount !== amounts.btc) {
Log("⚠️ BTC仓位数量不匹配!期望:", amounts.btc, "张 | 实际:", btcPosInfo.amount, "张")
// 如果BTC已经开了仓但数量不对,平掉重来
Log("🔄 BTC仓位异常,平仓后终止")
closeUnexpectedPosition(config.btcSymbol, "buy", btcPosInfo.amount)
return null
}
let btcAvgPrice = btcOrder ? btcOrder.avgPrice : btcPosInfo.price
Log("✅ BTC多仓已建立:", btcPosInfo.amount, "张 @", _N(btcAvgPrice, 2))
// ========== 第二步:开ETH空仓,持续尝试直到匹配 ==========
Log("📍 第2步:开ETH空仓", amounts.eth, "张,持续尝试直到成功")
let ethOrder = null
let ethPosInfo = {amount: 0, price: 0}
let maxRetries = 5
let retryCount = 0
while (retryCount < maxRetries) {
// 先检查当前ETH持仓
ethPosInfo = checkActualPosition(config.ethSymbol, "sell")
if (ethPosInfo.amount === amounts.eth) {
Log("✅ ETH空仓已完整:", ethPosInfo.amount, "张 @", _N(ethPosInfo.price, 2))
break
} else if (ethPosInfo.amount > 0 && ethPosInfo.amount < amounts.eth) {
// 补仓
let needAmount = amounts.eth - ethPosInfo.amount
Log("🔄 补开ETH空仓:", needAmount, "张 (当前:", ethPosInfo.amount, "/ 目标:", amounts.eth, ")")
ethOrder = createMarketOrder(config.ethSymbol, "sell", needAmount)
} else if (ethPosInfo.amount === 0) {
// 首次开仓
Log("🔄 开ETH空仓:", amounts.eth, "张 (尝试", retryCount + 1, "/", maxRetries, ")")
ethOrder = createMarketOrder(config.ethSymbol, "sell", amounts.eth)
} else if (ethPosInfo.amount > amounts.eth) {
Log("⚠️ ETH仓位超额!期望:", amounts.eth, "张 | 实际:", ethPosInfo.amount, "张")
// 平掉超额部分
let excessAmount = ethPosInfo.amount - amounts.eth
Log("🔄 平掉ETH超额仓位:", excessAmount, "张")
closeUnexpectedPosition(config.ethSymbol, "sell", excessAmount)
break
}
retryCount++
Sleep(1000)
}
// ========== 最终检查两腿是否匹配 ==========
btcPosInfo = checkActualPosition(config.btcSymbol, "buy")
ethPosInfo = checkActualPosition(config.ethSymbol, "sell")
if (btcPosInfo.amount === 0 || ethPosInfo.amount === 0) {
Log("❌ 开仓失败,清理残留仓位")
if (btcPosInfo.amount > 0) {
Log("🔄 清理BTC多仓:", btcPosInfo.amount, "张")
closeUnexpectedPosition(config.btcSymbol, "buy", btcPosInfo.amount)
}
if (ethPosInfo.amount > 0) {
Log("🔄 清理ETH空仓:", ethPosInfo.amount, "张")
closeUnexpectedPosition(config.ethSymbol, "sell", ethPosInfo.amount)
}
return null
}
let ethAvgPrice = ethPosInfo.price
Log("🟢 开仓完成 | BTC涨:", _N(btcChange*100, 2), "%",
"\n BTC:", btcPosInfo.amount, "张(", _N(btcPosInfo.amount * contractInfo.btcCtVal, 4), "币) @", _N(btcAvgPrice, 2),
"\n ETH:", ethPosInfo.amount, "张(", _N(ethPosInfo.amount * contractInfo.ethCtVal, 4), "币) @", _N(ethAvgPrice, 2))
return {
type: 'long_btc_short_eth',
btcPrice: btcAvgPrice,
ethPrice: ethAvgPrice,
btcAmount: btcPosInfo.amount,
ethAmount: ethPosInfo.amount,
btcContracts: btcPosInfo.amount,
ethContracts: ethPosInfo.amount,
btcCoinAmount: btcPosInfo.amount * contractInfo.btcCtVal,
ethCoinAmount: ethPosInfo.amount * contractInfo.ethCtVal,
beta: beta,
openTime: Date.now()
}
}
// 做空BTC + 做多ETH(优化版)
function openShortBtcLongEth(btcTicker, ethTicker, btcChange, beta) {
let amounts = calculateHedgeAmount(beta)
// ========== 第一步:开BTC空仓 ==========
Log("📍 第1步:开BTC空仓", amounts.btc, "张")
let btcOrder = createMarketOrder(config.btcSymbol, "sell", amounts.btc)
// 检查BTC实际持仓(无论订单是否成功都要检查)
let btcPosInfo = checkActualPosition(config.btcSymbol, "sell")
if (btcPosInfo.amount === 0) {
Log("❌ BTC未开仓成功,终止流程")
return null
}
if (btcPosInfo.amount !== amounts.btc) {
Log("⚠️ BTC仓位数量不匹配!期望:", amounts.btc, "张 | 实际:", btcPosInfo.amount, "张")
// 如果BTC已经开了仓但数量不对,平掉重来
Log("🔄 BTC仓位异常,平仓后终止")
closeUnexpectedPosition(config.btcSymbol, "sell", btcPosInfo.amount)
return null
}
let btcAvgPrice = btcOrder ? btcOrder.avgPrice : btcPosInfo.price
Log("✅ BTC空仓已建立:", btcPosInfo.amount, "张 @", _N(btcAvgPrice, 2))
// ========== 第二步:开ETH多仓,持续尝试直到匹配 ==========
Log("📍 第2步:开ETH多仓", amounts.eth, "张,持续尝试直到成功")
let ethOrder = null
let ethPosInfo = {amount: 0, price: 0}
let maxRetries = 5
let retryCount = 0
while (retryCount < maxRetries) {
// 先检查当前ETH持仓
ethPosInfo = checkActualPosition(config.ethSymbol, "buy")
if (ethPosInfo.amount === amounts.eth) {
Log("✅ ETH多仓已完整:", ethPosInfo.amount, "张 @", _N(ethPosInfo.price, 2))
break
} else if (ethPosInfo.amount > 0 && ethPosInfo.amount < amounts.eth) {
// 补仓
let needAmount = amounts.eth - ethPosInfo.amount
Log("🔄 补开ETH多仓:", needAmount, "张 (当前:", ethPosInfo.amount, "/ 目标:", amounts.eth, ")")
ethOrder = createMarketOrder(config.ethSymbol, "buy", needAmount)
} else if (ethPosInfo.amount === 0) {
// 首次开仓
Log("🔄 开ETH多仓:", amounts.eth, "张 (尝试", retryCount + 1, "/", maxRetries, ")")
ethOrder = createMarketOrder(config.ethSymbol, "buy", amounts.eth)
} else if (ethPosInfo.amount > amounts.eth) {
Log("⚠️ ETH仓位超额!期望:", amounts.eth, "张 | 实际:", ethPosInfo.amount, "张")
// 平掉超额部分
let excessAmount = ethPosInfo.amount - amounts.eth
Log("🔄 平掉ETH超额仓位:", excessAmount, "张")
closeUnexpectedPosition(config.ethSymbol, "buy", excessAmount)
break
}
retryCount++
Sleep(1000)
}
// ========== 最终检查两腿是否匹配 ==========
btcPosInfo = checkActualPosition(config.btcSymbol, "sell")
ethPosInfo = checkActualPosition(config.ethSymbol, "buy")
if (btcPosInfo.amount === 0 || ethPosInfo.amount === 0) {
Log("❌ 开仓失败,清理残留仓位")
if (btcPosInfo.amount > 0) {
Log("🔄 清理BTC空仓:", btcPosInfo.amount, "张")
closeUnexpectedPosition(config.btcSymbol, "sell", btcPosInfo.amount)
}
if (ethPosInfo.amount > 0) {
Log("🔄 清理ETH多仓:", ethPosInfo.amount, "张")
closeUnexpectedPosition(config.ethSymbol, "buy", ethPosInfo.amount)
}
return null
}
let ethAvgPrice = ethPosInfo.price
Log("🔴 开仓完成 | BTC跌:", _N(btcChange*100, 2), "%",
"\n BTC:", btcPosInfo.amount, "张(", _N(btcPosInfo.amount * contractInfo.btcCtVal, 4), "币) @", _N(btcAvgPrice, 2),
"\n ETH:", ethPosInfo.amount, "张(", _N(ethPosInfo.amount * contractInfo.ethCtVal, 4), "币) @", _N(ethAvgPrice, 2))
return {
type: 'short_btc_long_eth',
btcPrice: btcAvgPrice,
ethPrice: ethAvgPrice,
btcAmount: btcPosInfo.amount,
ethAmount: ethPosInfo.amount,
btcContracts: btcPosInfo.amount,
ethContracts: ethPosInfo.amount,
btcCoinAmount: btcPosInfo.amount * contractInfo.btcCtVal,
ethCoinAmount: ethPosInfo.amount * contractInfo.ethCtVal,
beta: beta,
openTime: Date.now()
}
}
// 检查平仓条件
function checkClose(pos, btcTicker, ethTicker) {
let btcPnlUsd, ethPnlUsd
if (pos.type === 'long_btc_short_eth') {
btcPnlUsd = (btcTicker.Last - pos.btcPrice) * pos.btcCoinAmount
ethPnlUsd = (pos.ethPrice - ethTicker.Last) * pos.ethCoinAmount
} else {
btcPnlUsd = (pos.btcPrice - btcTicker.Last) * pos.btcCoinAmount
ethPnlUsd = (ethTicker.Last - pos.ethPrice) * pos.ethCoinAmount
}
let totalPnlUsd = btcPnlUsd + ethPnlUsd
let totalCost = pos.btcPrice * pos.btcCoinAmount + pos.ethPrice * pos.ethCoinAmount
let totalPnlPct = totalPnlUsd / totalCost
if (totalPnlPct >= config.takeProfitPct) {
return {close: true, reason: '✅止盈', pnl: totalPnlPct, pnlUsd: totalPnlUsd}
}
if (totalPnlPct <= config.stopLossPct) {
return {close: true, reason: '🛑止损', pnl: totalPnlPct, pnlUsd: totalPnlUsd}
}
return {close: false, pnl: totalPnlPct, pnlUsd: totalPnlUsd}
}
// 平仓
function closePosition(pos, btcTicker, ethTicker) {
try {
if (pos.type === 'long_btc_short_eth') {
let btcClose = createMarketOrder(config.btcSymbol, "closebuy", pos.btcAmount)
let ethClose = createMarketOrder(config.ethSymbol, "closesell", pos.ethAmount)
if (btcClose && ethClose) {
Log("平仓成交 | BTC:", pos.btcAmount, "张 @", btcClose.avgPrice,
"| ETH:", pos.ethAmount, "张 @", ethClose.avgPrice)
return true
}
} else {
let btcClose = createMarketOrder(config.btcSymbol, "closesell", pos.btcAmount)
let ethClose = createMarketOrder(config.ethSymbol, "closebuy", pos.ethAmount)
if (btcClose && ethClose) {
Log("平仓成交 | BTC:", pos.btcAmount, "张 @", btcClose.avgPrice,
"| ETH:", pos.ethAmount, "张 @", ethClose.avgPrice)
return true
}
}
return false
} catch (e) {
Log("❌ 平仓失败:", e)
return false
}
}
function main() {
Log("策略启动 - BTC-ETH Beta对冲 (币数计算模式 + 优化开仓)")
if (!getContractValue()) {
Log("❌ 无法获取合约信息,策略退出")
return
}
let accountInfo = _C(exchange.GetAccount)
statistics.initialBalance = accountInfo.Equity
while (true) {
let btcRecords = exchange.GetRecords(config.btcSymbol, PERIOD_D1)
let ethRecords = exchange.GetRecords(config.ethSymbol, PERIOD_D1)
if (!btcRecords || btcRecords.length < config.betaLookback + 2 ||
!ethRecords || ethRecords.length < config.betaLookback + 2) {
Sleep(60000)
continue
}
let beta = calculateBeta(btcRecords, ethRecords, config.betaLookback)
let btcBar = btcRecords[btcRecords.length - 1]
let ethBar = ethRecords[ethRecords.length - 1]
let btcChange = (btcBar.Close - btcBar.Open) / btcBar.Open
let ethChange = (ethBar.Close - ethBar.Open) / ethBar.Open
let btcTicker = exchange.GetTicker(config.btcSymbol)
let ethTicker = exchange.GetTicker(config.ethSymbol)
if (!btcTicker || !ethTicker) {
Sleep(5000)
continue
}
// ✅ 更新胜率
if (statistics.lossCount > 0) {
statistics.profitLossRatio = statistics.winCount / (statistics.lossCount + statistics.winCount)
}
let positions = _C(exchange.GetPositions)
// 开仓逻辑
if (!currentPosition && positions.length === 0) {
if (btcChange > config.triggerThreshold && btcChange > ethChange) {
Log('BTC涨幅:', _N(btcChange*100, 2), '% | ETH涨幅:', _N(ethChange*100, 2), '%')
let pos = openLongBtcShortEth(btcTicker, ethTicker, btcChange, beta)
if (pos) currentPosition = pos
}
else if (btcChange < -config.triggerThreshold && btcChange < ethChange) {
Log('BTC跌幅:', _N(btcChange*100, 2), '% | ETH跌幅:', _N(ethChange*100, 2), '%')
let pos = openShortBtcLongEth(btcTicker, ethTicker, btcChange, beta)
if (pos) currentPosition = pos
}
}
// ✅ 平仓逻辑 - 累加已实现盈亏
if (currentPosition && positions.length === 2) {
let result = checkClose(currentPosition, btcTicker, ethTicker)
if (result.close) {
// 先记录平仓盈亏
let closePnl = result.pnlUsd
let closed = closePosition(currentPosition, btcTicker, ethTicker)
if (closed) {
// ✅ 累加已实现盈亏
statistics.realizedProfit += closePnl
Log("平仓 |", result.reason,
"| 本次盈亏: $", _N(closePnl, 2),
"(", _N(result.pnl*100, 2), "%)",
"| 累计已实现: $", _N(statistics.realizedProfit, 2))
if (result.reason.includes('止盈')) {
statistics.takeProfitCount++
}
if (result.reason.includes('止损')) {
statistics.stopLossCount++
}
if (closePnl > 0) {
statistics.winCount++
} else {
statistics.lossCount++
}
currentPosition = null
}
}
}
updateStatusTable(btcTicker, ethTicker)
Sleep(1000)
}
}