
Gần đây, một người bạn hỏi tôi có thể tạo ra một chiến lược giao dịch chênh lệch giá hay không, vì nền tảng Inventor lại thiếu chức năng này. Tôi nghĩ việc đó không khó, nhưng phải mất khoảng một tháng chỉ để hoàn thiện được phần logic cơ bản. Nhìn lại bây giờ, những khó khăn tôi gặp phải từ ý tưởng ban đầu đến khi triển khai thực tế nhiều hơn tôi tưởng tượng rất nhiều.
Bài viết này ghi lại những vấn đề thực tiễn gặp phải và các giải pháp được phát triển trong quá trình xây dựng chiến lược chênh lệch giá này.Chỉ dùng cho mục đích học tập và tham khảo; không có giá trị đầu tư thực tế.。
Có sự chênh lệch giá giữa giá hợp đồng giao hàng và giá giao ngay, thường dao động quanh một mức trung bình nhất định. Khi sự chênh lệch giá quá lớn, về mặt lý thuyết, sẽ xuất hiện các cơ hội kinh doanh chênh lệch giá.
Ý tưởng ban đầu rất đơn giản:
Nghe có vẻ tuyệt vời, phải không? Nhưng trên thực tế, bạn sẽ thấy rằng chỉ riêng bước “mở vị thế” thôi cũng đã có vô số chi tiết cần xử lý.
Ban đầu, chúng tôi sử dụng độ lệch chênh lệch giá làm chỉ báo trực tiếp, nhưng chúng tôi nhận thấy rằng đôi khi chênh lệch giá sẽ tiếp tục mở rộng và không trở lại trạng thái trước đó. Sau đó, chúng tôi nhận ra…Không phải tất cả chênh lệch giá đều ổn định.。
Đặc biệt khi ngày giao hàng đến gần, diễn biến của chênh lệch giá có thể thay đổi. Do đó, bài kiểm tra ADF đã được thêm vào để xác định xem chuỗi chênh lệch giá có ổn định hay không.
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));
// ... ADF检验的核心计算逻辑
const res = ols(rows, Ys);
const tstat = tStat(res.beta, res.cov, 1);
return { tStat: tstat, pValue: pval, usedLag: p };
}
Ban đầu, chúng tôi đã thêm một loạt các bài kiểm tra, bao gồm kiểm tra tỷ lệ phương sai, kiểm tra thời gian bán hủy và kiểm tra KS, nhưng chúng tôi nhận thấy rằng quá nhiều bài kiểm tra đã làm giảm đáng kể số lượng cơ hội mở vị trí. Cuối cùng, chúng tôi đã đơn giản hóa chỉ còn kiểm tra ADF và đặt ngưỡng giá trị p là 0,1.
Quan trọng hơn, một cái nữa đã được thêm vào.Bộ đếm lỗi liên tục:
if (!adfPass) {
stationarityFailCount[deliverySymbol] = (stationarityFailCount[deliverySymbol] || 0) + 1;
} else {
stationarityFailCount[deliverySymbol] = 0;
}
let consecutiveFails = stationarityFailCount[deliverySymbol];
let canTrade = consecutiveFails < CONFIG.consecutiveFailThreshold;
Giao dịch bị cấm nếu bài kiểm tra thất bại ba lần liên tiếp. Điều này có thể ngăn chặn việc mở vị thế một cách mù quáng khi thị trường đang ở trạng thái bất thường.
Tính toán lãi lỗ cho tài khoản giao dịch tương lai rất đơn giản; chỉ cần xem xét sự thay đổi về USDT. Nhưng tài khoản giao ngay thì khác; chúng chứa cả USDT và tiền điện tử. Vậy làm thế nào để tính toán điều đó?
Đây là một chi tiết rất dễ bị bỏ qua:Chi phí lưu giữ hàng hóa giao ngay sẽ thay đổi theo hoạt động giao dịch.Ví dụ, nếu bạn mua một đồng tiền với giá 100 USDT, bán nó với giá 90 USDT, và sau đó mua lại với giá 85 USDT, thì giá vốn của bạn không còn là 100 USDT ban đầu nữa. Việc chỉ sử dụng giá cố định tại thời điểm mở vị thế để tính toán giá trị của đồng tiền sẽ không phản ánh đúng tình hình lãi lỗ thực tế.
Cách tiếp cận đúng làTrích xuất giá giao dịch trung bình thực tế từ đối tượng đơn hàng.:
let openSpotPrice = (openSpotOrder && openSpotOrder.AvgPrice) ?
openSpotOrder.AvgPrice : record.openSpotPrice;
let closeSpotPrice = closeSpotOrder.AvgPrice || currentPair.spotPrice;
Sau đó, tỷ suất lợi nhuận được tính toán dựa trên giá giao dịch thực tế:
// 正套:买现货+卖期货
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;
Hãy lưu ý logic tính toán lãi và lỗ ở đây:
Cuối cùng, lợi nhuận hoặc thua lỗ thực tế của mỗi giao dịch được cộng lại để ra tổng lợi nhuận hoặc thua lỗ:
accumulatedProfit += actualTotalPnl;
_G('accumulatedProfit', accumulatedProfit);
Đây mới thực sự là vấn đề đau đầu. Tại sao, sau 10 năm hoạt động, nền tảng Inventor Quantitative Platform lại có quá ít chiến lược giao dịch chênh lệch giá cho hợp đồng tương lai? Câu trả lời rất đơn giản:Thiếu thanh khoản trên thị trường hợp đồng giao hàng。
Giao dịch chênh lệch giá không phải là chuyện cổ tích mà bạn có thể mở các vị thế cùng một lúc. Thực tế là:
Đây là một ví dụ điển hình về “rủi ro một chân”. Một chân đã tham gia, nhưng chân kia vẫn ở ngoài. Khi giá biến động, nó không còn là giao dịch chênh lệch giá nữa mà là một vị thế một chiều.
Giải pháp là tham giaCơ chế hoàn tác:
if (!deliveryOrder) {
Log('❌ 期货卖单失败,回滚现货');
exchanges[0].CreateOrder(pair.spotSymbol, 'sell', -1, spotAmount);
addCooldown(pair.deliverySymbol, pair.coin, '期货卖单失败,已回滚现货');
return false;
}
Nếu bất kỳ giao dịch nào thất bại, hãy đặt lệnh thị trường ngay lập tức để đóng giao dịch đã thực hiện, giảm thiểu rủi ro cho giao dịch đó.
Điều nực cười hơn nữa là đôi khi các lệnh thị trường cũng thất bại. Điều này có thể do các biện pháp kiểm soát rủi ro của sàn giao dịch hoặc độ sâu thị trường không đủ; nói tóm lại, lệnh đơn giản là không được thực hiện.
Vậy là tôi đã làm rồi.Cơ chế kép của lệnh thị trường + lệnh giới hạn:
function createOrderWithFallback(exchange, symbol, direction, amount, limitPrice, orderType, maxRetry = 3) {
let useMarketOrder = (limitPrice === -1);
// 先尝试限价单
if (!useMarketOrder) {
orderId = exchange.CreateOrder(symbol, direction, limitPrice, amount);
if (!orderId) {
Log(`❌ 限价单提交失败,改用市价单`);
useMarketOrder = true;
}
}
// 限价单失败则用市价单
if (useMarketOrder && !orderId) {
orderId = exchange.CreateOrder(symbol, direction, -1, marketAmount);
}
// ... 检查订单状态,失败则重试
}
Hệ thống có thể thử lại tối đa 3 lần, mỗi lần thử đều sử dụng lệnh thị trường làm biện pháp dự phòng.
Cạm bẫy này đặc biệt khó nhận biết và đòi hỏi sự thận trọng tối đa. Số lượng lệnh giao dịch trên thị trường tương lai là…Số lượng đồng xuTuy nhiên, số lượng đặt hàng khi mua lệnh trên thị trường giao ngay là…Số lượng USDT!
Tại đây, logic chuyển đổi đã được thêm vào một cách cụ thể:
function getActualAmount(useMarket) {
if (isSpotBuy && useMarket) {
// 现货市价买单:需要用USDT金额
let currentPrice = getDepthMidPrice(exchange, symbol);
let usdtAmount = amount * currentPrice;
Log(` 💡 现货买单转换: ${amount.toFixed(6)} 币 → ${usdtAmount.toFixed(4)} USDT`);
return usdtAmount;
}
return amount;
}
Lượng tiền điện tử được sử dụng trong lệnh giới hạn sẽ tự động được chuyển đổi sang USDT cho lệnh thị trường.
Một vấn đề gây khó chịu khác là khi tín hiệu chênh lệch giá được phát hiện, và bạn đã sẵn sàng mở vị thế, nhưng cơ hội đã biến mất vào thời điểm bạn đặt lệnh.
Giá cả biến động theo thời gian thực, và điều kiện thị trường có thể đã thay đổi giữa thời điểm phát hiện tín hiệu và thời điểm thực hiện lệnh. Chênh lệch giá có thể đã thu hẹp, và các cơ hội kinh doanh chênh lệch giá có thể đã biến mất. Nếu bạn vẫn dại dột mở lệnh vào thời điểm này, bạn chỉ đang phí hoa hồng mà thôi.
Vì vậy, nó đã được thêm vào.Cơ chế xác nhận thứ cấp:
// 开仓前重新获取实时价格并验证套利机会
Log('🔄 重新获取实时Depth盘口价格并验证套利机会...');
let realtimeSpotPrice = getDepthMidPrice(exchanges[0], pair.spotSymbol, true);
let realtimeDeliveryPrice = getDepthMidPrice(exchanges[1], pair.deliverySymbol, true);
let realtimeSpread = realtimeDeliveryPrice - realtimeSpotPrice;
let realtimeSpreadRate = realtimeSpread / realtimeSpotPrice;
// 重新计算实时Z-Score
let realtimeZScore = (realtimeSpreadRate - mu) / (sigma || 1e-6);
// 验证:实时Z-Score是否仍然满足开仓条件
if (absRealtimeZ < CONFIG.zScoreEntry) {
Log('❌ 套利机会已消失!');
Log(' 取消开仓,避免亏损');
return false;
}
Trước khi thực sự đặt lệnh, hãy lấy lại giá thời gian thực, tính toán lại điểm Z, và chỉ thực hiện lệnh nếu bạn xác nhận rằng cơ hội vẫn còn.
Đôi khi, khi một chiến lược được khởi động lại hoặc một vị thế trước đó được đóng lại, có thể còn sót lại các vị thế trong tài khoản hợp đồng tương lai. Nếu không xử lý những vị thế này, các vị thế mới sẽ chồng chéo với các vị thế cũ, dẫn đến tình trạng mất kiểm soát.
Vì vậy, nó đã được thêm vào.Thanh lý bắt buộc trước khi mở vị thếLý do:
// 检查期货现有仓位并平仓
let existingPosition = getPositionBySymbol(pair.deliverySymbol);
if (existingPosition && Math.abs(existingPosition.Amount) > 0) {
Log('⚠️ 检测到该合约的现有仓位,执行平仓操作...');
let closeDirection = existingPosition.Type === PD_LONG ? 'closebuy' : 'closesell';
let closeAmount = Math.abs(existingPosition.Amount);
let closeOrder = createOrderWithFallback(
exchanges[1],
pair.deliverySymbol,
closeDirection,
closeAmount,
-1,
'期货'
);
if (!closeOrder) {
Log('❌ 平仓现有持仓失败,终止开仓');
addCooldown(pair.deliverySymbol, pair.coin, '平仓现有持仓失败');
return false;
}
}
Trước khi mở lệnh, hãy kiểm tra kỹ. Nếu còn lệnh nào chưa đóng, hãy đóng chúng để đảm bảo tài khoản của bạn được sạch sẽ.
Đây là toàn bộ quy trình xây dựng chiến lược.Bí mật nhất và nguy hiểm nhấtMột trong những vấn đề.
Sau khi chiến lược bắt đầu hoạt động, một hiện tượng kỳ lạ đã được quan sát thấy:
Thử dùng lệnh thị trường xem sao? Kết quả còn tệ hơn nữa:
Chuyện gì đã thực sự xảy ra? Sau nhiều lần so sánh dữ liệu giao dịch trực tiếp của sàn giao dịch, vấn đề cuối cùng đã được phát hiện:
Dữ liệu mã chứng khoán phản ánh giá giao dịch thực tế gần nhất.Nghe thì có vẻ ổn, nhưng đối với các thị trường có tính thanh khoản thấp như hợp đồng giao nhận, vấn đề sẽ phát sinh:
时间轴:
10:00:00 - 有人以50000成交了1张合约 → Ticker价格更新为50000
10:00:05 - 盘口挂单:买49800 / 卖50200(但没有成交)
10:00:10 - 盘口挂单:买49850 / 卖50150(但没有成交)
...
10:05:00 - Ticker价格仍然是50000(因为5分钟内没有新的成交)
Bạn có nhận thấy vấn đề đó không?Mức giá 50.000 của cổ phiếu đã trở thành dĩ vãng cách đây 5 phút.Tuy nhiên, giá thị trường thực tế hiện tại (giá sổ lệnh) có thể đã lên tới 49850⁄50150.
Nếu bạn sử dụng giá Ticker là 50.000 để tính toán các cơ hội chênh lệch giá và đặt lệnh giới hạn, thì:
Tính thanh khoản của hợp đồng giao hàng kém hơn nhiều so với hợp đồng giao ngay:
Đối với các hợp đồng có tính thanh khoản thấp, sự chênh lệch giữa giá niêm yết và giá thực tế trên sổ lệnh có thể lên tới:
Đối với các chiến lược kinh doanh chênh lệch giá, sự sai lệch này là tai hại. Lợi thế chênh lệch giá dự kiến có thể chỉ là 0,5%, nhưng giá giao dịch thực tế lại hoàn toàn khác.
Vì Ticker không đáng tin cậy, chúng ta hãy sử dụng…Dữ liệu độ sâu (độ sâu sổ lệnh):
function getDepthMidPrice(exchange, symbol, logDetail = false) {
let depth = exchange.GetDepth(symbol);
if (!depth || !depth.Bids || depth.Bids.length === 0 ||
!depth.Asks || depth.Asks.length === 0) {
Log(`❌ 获取${symbol}盘口失败`);
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;
}
Ưu điểm của dữ liệu độ sâu:
Cuối cùng được thông quaGiải pháp kết hợp giữa Ticker và Depth:
1. Sử dụng Ticker để duy trì chuỗi dữ liệu lịch sử.
// 用Ticker更新历史价差序列(保持连续性)
let spotTicker = exchanges[0].GetTicker(pair.spotSymbol);
let deliveryTicker = exchanges[1].GetTicker(pair.deliverySymbol);
pair.spotPrice = spotTicker.Last;
pair.deliveryPrice = deliveryTicker.Last;
pair.spread = pair.deliveryPrice - pair.spotPrice;
// 历史序列用于ADF检验、Z-Score计算
priceHistory[pair.deliverySymbol].push({
time: Date.now(),
spreadRate: pair.spread / pair.spotPrice,
spread: pair.spread,
spotPrice: pair.spotPrice,
deliveryPrice: pair.deliveryPrice
});
Tại sao Ticker vẫn được sử dụng cho dữ liệu lịch sử? Bởi vì nó cần thiết.Tính liên tục của dữ liệuNếu dữ liệu lịch sử cũng được biểu diễn bằng Độ sâu, sự biến động về giá trong sổ lệnh sẽ gây ra sự gián đoạn trong chuỗi dữ liệu lịch sử, ảnh hưởng đến độ chính xác của phân tích thống kê.
2. Sử dụng độ sâu để đánh giá theo thời gian thực và xác minh vị trí mở.
// 开仓前用Depth重新验证套利机会
let realtimeSpotPrice = getDepthMidPrice(exchanges[0], pair.spotSymbol, true);
let realtimeDeliveryPrice = getDepthMidPrice(exchanges[1], pair.deliverySymbol, true);
// 基于Depth价格重新计算Z-Score
let realtimeSpread = realtimeDeliveryPrice - realtimeSpotPrice;
let realtimeSpreadRate = realtimeSpread / realtimeSpotPrice;
let realtimeZScore = (realtimeSpreadRate - mu) / (sigma || 1e-6);
// 二次验证:套利机会是否仍然存在
if (Math.abs(realtimeZScore) < CONFIG.zScoreEntry) {
Log('❌ 套利机会已消失(基于Depth实时价格)');
return false;
}
3. Tính giá lệnh giới hạn bằng cách sử dụng Độ sâu.
// 基于Depth价格和平均价差计算限价单价格
let spreadDeviation = realtimeSpread - avgSpread;
let adjustmentRatio = Math.min(
Math.abs(spreadDeviation) * CONFIG.limitOrderSpreadRatio,
spreadStd * 0.5
);
if (direction === 'positive') {
spotLimitPrice = realtimeSpotPrice + adjustmentRatio;
deliveryLimitPrice = realtimeDeliveryPrice - adjustmentRatio;
} else {
spotLimitPrice = realtimeSpotPrice - adjustmentRatio;
deliveryLimitPrice = realtimeDeliveryPrice + adjustmentRatio;
}
Giá lệnh giới hạn được tính theo cách này dựa trên sổ lệnh thực tế, điều này làm tăng đáng kể xác suất khớp lệnh.
4. Tính toán lãi và lỗ theo thời gian thực bằng cách sử dụng Độ sâu.
function calculateUnrealizedPnL(record, currentPair) {
// 优先用Depth价格计算实时盈亏
let currentSpotPrice = getDepthMidPrice(exchanges[0], currentPair.spotSymbol);
let currentDeliveryPrice = getDepthMidPrice(exchanges[1], currentPair.deliverySymbol);
// Depth获取失败才回退到Ticker
if (!currentSpotPrice || !currentDeliveryPrice) {
currentSpotPrice = currentPair.spotPrice;
currentDeliveryPrice = currentPair.deliveryPrice;
}
// 计算盈亏...
}
Các vấn đề khi sử dụng Ticker:
检测到套利信号(基于Ticker)
→ 计算限价单价格
→ 下单等待
→ 长时间不成交(价格已经不对了)
→ 改用市价单
→ 成交价格和预期差很多
→ 套利失败或微利
Những cải tiến sau khi sử dụng Depth:
检测到套利信号(基于Ticker历史)
→ 用Depth重新验证(机会仍在)
→ 基于Depth计算限价单价格
→ 下单,价格贴近盘口
→ 较快成交
→ 成交价格符合预期
→ 套利成功
Nếu chúng ta sử dụng lệnh giới hạn, làm thế nào để thiết lập giá? Nếu thiết lập quá cao, giao dịch sẽ không thành công; nếu thiết lập quá thấp, chúng ta sẽ không có được mức giá tốt.
Dựa trên giá theo độ sâu, phương pháp tiếp cận ở đây là:Tự động điều chỉnh dựa trên sự chênh lệch giữa biên độ giá hiện tại và biên độ giá trung bình.。
let spreadDeviation = realtimeSpread - avgSpread;
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));
Nếu đó là một bộ đầy đủ (với sự chênh lệch giá lớn):
Điều này cho phép các giao dịch được hoàn tất với mức chênh lệch giá thuận lợi, đồng thời tránh việc đặt lệnh quá xa sổ lệnh và do đó không dẫn đến bỏ lỡ giao dịch.
Bất kỳ nỗ lực mở lệnh nào không thành công đều cho thấy có vấn đề trên thị trường, có thể do thanh khoản không đủ hoặc biến động quá mức. Trong những trường hợp như vậy, bạn không nên thử lại ngay lập tức; thay vào đó, bạn nên giữ bình tĩnh.
Do đó, một khoản phạt đã được thêm vào mỗi cặp giao dịch thất bại.Thời gian hồi phục 10 phút:
function addCooldown(deliverySymbol, coin, reason) {
pairCooldowns[deliverySymbol] = Date.now() + CONFIG.cooldownDuration;
Log(`⏸️ ${deliverySymbol} 进入10分钟冷却期`);
Log(` 原因: ${reason}`);
_G('pairCooldowns', pairCooldowns);
}
Trong thời gian chờ, sẽ không có vị thế nào được mở cho cặp giao dịch này để tránh các lỗi lặp lại và phí giao dịch bị lãng phí.
Chiến lược này vẫn đang trong quá trình hoàn thiện và còn nhiều lĩnh vực có thể được tối ưu hóa:
1. Vấn đề chậm trễ Hiện tại, giá cả được lấy bằng phương pháp thăm dò định kỳ, dẫn đến độ trễ đáng kể. Việc chuyển sang sử dụng WebSocket để cập nhật giá theo thời gian thực sẽ cải thiện đáng kể tốc độ phản hồi.
2. Tối ưu hóa kiểm soát rủi ro Phương pháp cắt lỗ hiện tại tương đối đơn giản và dễ hiểu, bạn có thể xem xét:
3. Quản lý trượt giá Chiến lược định giá cho lệnh giới hạn có thể được thực hiện thông minh hơn, chẳng hạn như tự động điều chỉnh dựa trên các yếu tố như độ sâu sổ lệnh và khối lượng giao dịch gần đây.
4. Các ứng dụng khác của dữ liệu độ sâu Nó có thể phân tích sự mất cân bằng của sổ lệnh, dự đoán xu hướng giá cả và nâng cao tỷ lệ thành công của giao dịch chênh lệch giá.
Các chiến lược kinh doanh chênh lệch giá nghe có vẻ hấp dẫn, nhưng trên thực tế, rõ ràng là có vô số cạm bẫy giữa lý tưởng và hiện thực.
Đặc biệt, vấn đề dữ liệu Ticker bị chậm trễ là một trở ngại trong toàn bộ quá trình xây dựng chiến lược.Dễ bị bỏ qua nhất nhưng lại có tác động lớn nhất.Những cạm bẫy. Đối với thị trường hợp đồng giao hàng có tính thanh khoản thấp:
Nguyên tắc cốt lõi: Sử dụng mã chứng khoán (Ticker) để duy trì tính liên tục lịch sử, và sử dụng dữ liệu chiều sâu (Depth) để nắm bắt các cơ hội trong thời gian thực.
Bài viết này ghi lại những vấn đề gặp phải và các giải pháp trong quá trình thăm dò, hy vọng có thể cung cấp một số thông tin tham khảo cho mọi người.Xin nhắc lại, bài viết này chỉ nhằm mục đích giáo dục và thảo luận. Mã nguồn vẫn đang trong quá trình phát triển và không nên được sử dụng trực tiếp trong giao dịch thực tế.。
Nếu bạn đang sử dụng chiến lược tương tự, đừng ngần ngại thảo luận với tôi. Thị trường rất phức tạp, và chính sự phức tạp này khiến giao dịch định lượng trở nên đầy thách thức.
Mã nguồn chiến lược: https://www.fmz.com/strategy/519280