本策略的灵感来源于知乎作者“梦想贩子”的机会贴——“TRUMP与MELANIA低风险关联套利模式”。文章通过对两个在BN上线的合约(TRUMP与MELANIA)的价格关联性进行挖掘,并利用二者间微妙的时间延迟,尝试捕捉市场短期波动,从而实现低风险套利。接下来,我们将详细说明该策略的原理、代码实现逻辑,并探讨可能的优化方向。
需要事先注意的是,本策略相当于一个手动的交易工作,只有在找到合适的两个交易对的基础上,才具有一定的盈利机会,且该交易对的盈利寿命可能较短,当发现不存在盈利机会时,需要及时停止策略,防止利润回撤甚至亏损。
TRUMP和MELANIA这两个合约均由同一发行团队发行,且控盘资金相同,因此两者价格走势在大部分时间内高度同步。然而,由于合约设计或市场执行等因素,MELANIA的价格往往会滞后于TRUMP 1-2秒。这种微小的延迟为套利者提供了捕捉价格差异、进行高频跟单交易的机会。简单来说,当TRUMP发生急速波动时,MELANIA往往会在极短时间后跟进,通过利用这个延迟,便可以在风险较低的情况下完成交易。
在加密市场,类似的关联性现象并不少见: - 同一项目的不同合约或衍生品:由于相同的底层资产或团队背景,不同产品间价格常有较强的联动性。 - 跨交易所套利:不同交易所的同一资产因流动性、撮合机制差异也会产生微小的价差。 - 稳定币与法币挂钩产品:这些产品往往存在预期的汇率偏差,套利者可利用微幅波动获利。
这种关联性为高频交易者和套利者提供了稳定的交易信号和较低风险的操作机会,但同时也要求交易策略对市场微妙变化具有高度敏感性和实时响应能力。
代码主要由几个部分构成,每个模块都对应了套利策略中的关键步骤。
function GetPosition(pair){
let pos = exchange.GetPosition(pair)
if(pos.length == 0){
return {amount:0, price:0, profit:0}
}else if(pos.length > 1){
throw '不支持双向持仓'
}else if(pos.length == 1){
return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
}else{
Log('未获取仓位数据')
return null
}
}
function InitAccount(){
let account = _C(exchange.GetAccount)
let total_eq = account.Equity
let init_eq = 0
if(!_G('init_eq')){
init_eq = total_eq
_G('init_eq', total_eq)
}else{
init_eq = _G('init_eq')
}
return init_eq
}
function CancelPendingOrders() {
orders = exchange.GetOrders(); // 获取订单
for (let order of orders) {
if (order.Status == ORDER_STATE_PENDING) { // 只取消未完成的订单
exchange.CancelOrder(order.Id); // 取消挂单
}
}
}
主函数采用无限循环不断执行以下步骤:
数据获取与行情计算
每次循环开始,通过 exchange.GetRecords
分别获取 Pair_A 与 Pair_B 的行情数据。
开仓条件判断与下单
当当前没有仓位(position_B.amount == 0)且交易允许(afterTrade==1)时:
止盈止损逻辑
一旦仓位建立,策略会根据持仓方向设置相应的止盈与止损订单:
平仓后收益统计与日志记录
每次平仓后,系统会获取账户权益变化,并统计盈利次数、亏损次数、累计盈利/亏损金额。
同时,利用表格和图形实时展示当前持仓信息、交易统计数据及循环延时,方便后续策略效果分析。
虽然该策略利用了两个高度关联合约之间的微妙延迟进行套利,但仍有不少可以改进之处:
本文详细介绍了基于一个短时滞后合约间关联套利策略的基本原理与实现代码。从利用价格涨跌幅差异捕捉入场机会,到设置止盈止损进行仓位管理,该策略充分利用了加密市场中资产之间的高度关联性。与此同时,我们也提出了包括参数动态调整、信号过滤、系统健壮性和代码优化等多项优化建议,以期在实盘应用中进一步提升策略的稳定性和盈利能力。
策略虽灵感独到、实现简洁,但在高频且波动剧烈的加密市场中,任何套利操作都需谨慎对待。希望这篇文章能为热衷于量化交易和套利策略的朋友们提供有价值的参考和启发。
注:该策略测试环境为OKX模拟盘,可针对不同交易所修改具体细节
function GetPosition(pair){
let pos = exchange.GetPosition(pair)
if(pos.length == 0){
return {amount:0, price:0, profit:0}
}else if(pos.length > 1){
throw '不支持双向持仓'
}else if(pos.length == 1){
return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
}else{
Log('未获取仓位数据')
return null
}
}
function InitAccount(){
let account = _C(exchange.GetAccount)
let total_eq = account.Equity
let init_eq = 0
if(!_G('init_eq')){
init_eq = total_eq
_G('init_eq', total_eq)
}else{
init_eq = _G('init_eq')
}
return init_eq
}
function CancelPendingOrders() {
orders = exchange.GetOrders(); // 获取订单
for (let order of orders) {
if (order.Status == ORDER_STATE_PENDING) { // 只取消未完成的订单
exchange.CancelOrder(order.Id); // 取消挂单
}
}
}
var pair_a = Pair_A + "_USDT.swap";
var pair_b = Pair_B + "_USDT.swap";
function main() {
exchange.IO('simulate', true);
LogReset(0);
Log('策略开始运行')
var precision = exchange.GetMarkets();
var ratio = 0
var takeProfitOrderId = null;
var stopLossOrderId = null;
var successCount = 0;
var lossCount = 0;
var winMoney = 0;
var failMoney = 0;
var afterTrade = 1;
var initEq = InitAccount();
var curEq = initEq
var pricePrecision = precision[pair_b].PricePrecision;
while (true) {
try{
let startLoopTime = Date.now();
let position_B = GetPosition(pair_b);
let new_r_pairB = exchange.GetRecords(pair_b, 1).slice(-1)[0];
if (!new_r_pairB || !position_B) {
Log('跳过当前循环');
continue;
}
// 合并交易条件:检查是否可以开仓并进行交易
if (afterTrade == 1 && position_B.amount == 0) {
let new_r_pairA = exchange.GetRecords(pair_a, 1).slice(-1)[0];
if (!new_r_pairA ) {
Log('跳过当前循环');
continue;
}
ratio = (new_r_pairA.Close - new_r_pairA.Open) / new_r_pairA.Open - (new_r_pairB.Close - new_r_pairB.Open) / new_r_pairB.Open;
if (ratio > diffLevel) {
CancelPendingOrders();
Log('实时ratio:', ratio, '买入:', pair_b, position_B.amount);
exchange.CreateOrder(pair_b, "buy", -1, Trade_Number);
afterTrade = 0;
} else if (ratio < -diffLevel) {
CancelPendingOrders();
Log('实时ratio:', ratio, '卖出:', pair_b, position_B.amount);
exchange.CreateOrder(pair_b, "sell", -1, Trade_Number);
afterTrade = 0;
}
}
// 判断止盈止损
if (position_B.amount > 0 && takeProfitOrderId == null && stopLossOrderId == null && afterTrade == 0) {
Log('多仓持仓价格:', position_B.price, '止盈价格:', position_B.price * (1 + stopProfitLevel), '止损价格:', position_B.price * (1 - stopLossLevel));
takeProfitOrderId = exchange.CreateOrder(pair_b, "closebuy", position_B.price * (1 + stopProfitLevel), position_B.amount);
Log('止盈订单:', takeProfitOrderId);
}
if (position_B.amount > 0 && takeProfitOrderId != null && stopLossOrderId == null && new_r_pairB.Close < position_B.price * (1 - stopLossLevel) && afterTrade == 0) {
CancelPendingOrders();
takeProfitOrderId = null
Log('多仓止损');
stopLossOrderId = exchange.CreateOrder(pair_b, "closebuy", -1, position_B.amount);
Log('多仓止损订单:', stopLossOrderId);
}
if (position_B.amount < 0 && takeProfitOrderId == null && stopLossOrderId == null && afterTrade == 0) {
Log('空仓持仓价格:', position_B.price, '止盈价格:', position_B.price * (1 - stopProfitLevel), '止损价格:', position_B.price * (1 + stopLossLevel));
takeProfitOrderId = exchange.CreateOrder(pair_b, "closesell", position_B.price * (1 - stopProfitLevel), -position_B.amount);
Log('止盈订单:', takeProfitOrderId, '当前价格:', new_r_pairB.Close );
}
if (position_B.amount < 0 && takeProfitOrderId != null && stopLossOrderId == null && new_r_pairB.Close > position_B.price * (1 + stopLossLevel) && afterTrade == 0) {
CancelPendingOrders();
takeProfitOrderId = null
Log('空仓止损');
stopLossOrderId = exchange.CreateOrder(pair_b, "closesell", -1, -position_B.amount);
Log('空仓止损订单:', stopLossOrderId);
}
// 平市价单未完成
if (takeProfitOrderId == null && stopLossOrderId != null && afterTrade == 0) {
let stoplosspos = GetPosition(pair_b)
if(stoplosspos.amount > 0){
Log('平多仓市价单未完成')
exchange.CreateOrder(pair_b, 'closebuy', -1, stoplosspos.amount)
}
if(stoplosspos.amount < 0){
Log('平空仓市价单未完成')
exchange.CreateOrder(pair_b, 'closesell', -1, -stoplosspos.amount)
}
}
// 未平仓完毕
if (Math.abs(position_B.amount) < Trade_Number && Math.abs(position_B.amount) > 0 && afterTrade == 0){
Log('未平仓完毕')
if(position_B.amount > 0){
exchange.CreateOrder(pair_b, 'closebuy', -1, position_B.amount)
}else{
exchange.CreateOrder(pair_b, 'closesell', -1, -position_B.amount)
}
}
// 计算盈亏
if (position_B.amount == 0 && afterTrade == 0) {
if (stopLossOrderId != null || takeProfitOrderId != null) {
stopLossOrderId = null;
takeProfitOrderId = null;
let afterEquity = exchange.GetAccount().Equity;
let curAmount = afterEquity - curEq;
curEq = afterEquity
if (curAmount > 0) {
successCount += 1;
winMoney += curAmount;
Log('盈利金额:', curAmount);
} else {
lossCount += 1;
failMoney += curAmount;
Log('亏损金额:', curAmount);
}
afterTrade = 1;
}
}
if (startLoopTime % 10 == 0) { // 每 10 次循环记录一次
let curEquity = exchange.GetAccount().Equity
// 输出交易信息表
let table = {
type: "table",
title: "交易信息",
cols: [
"初始权益", "当前权益", Pair_B + "仓位", Pair_B + "持仓价", Pair_B + "收益", Pair_B + "价格",
"盈利次数", "盈利金额", "亏损次数", "亏损金额", "胜率", "盈亏比"
],
rows: [
[
_N(_G('init_eq'), 2), // 初始权益
_N(curEquity, 2), // 当前权益
_N(position_B.amount, 1), // Pair B 仓位
_N(position_B.price, pricePrecision), // Pair B 持仓价
_N(position_B.profit, 1), // Pair B 收益
_N(new_r_pairB.Close, pricePrecision), // Pair B 价格
_N(successCount, 0), // 盈利次数
_N(winMoney, 2), // 盈利金额
_N(lossCount, 0), // 亏损次数
_N(failMoney, 2), // 亏损金额
_N(successCount + lossCount === 0 ? 0 : successCount / (successCount + lossCount), 2), // 胜率
_N(failMoney === 0 ? 0 : winMoney / failMoney * -1, 2) // 盈亏比
]
]
};
$.PlotMultLine("ratio plot", "幅度变化差值", ratio, startLoopTime);
$.PlotMultHLine("ratio plot", diffLevel, "差价上限", "red", "ShortDot");
$.PlotMultHLine("ratio plot", -diffLevel, "差价下限", "blue", "ShortDot");
LogStatus("`" + JSON.stringify(table) + "`");
LogProfit(curEquity - initEq, '&')
}
}catch(e){
Log('策略出现错误:', e)
}
Sleep(200);
}
}