
L’inspiration de cette stratégie vient de l’article d’opportunité de l’auteur de Zhihu “Dream Dealer” - “TRUMP et MELANIA modèle d’arbitrage de corrélation à faible risque”. L’article explore la corrélation des prix entre deux contrats lancés sur BN (TRUMP et MELANIA) et utilise le délai subtil entre les deux pour tenter de capturer les fluctuations du marché à court terme et de réaliser un arbitrage à faible risque. Ensuite, nous expliquerons les principes de cette stratégie, la logique d’implémentation du code et explorerons les directions d’optimisation possibles.
Il faut être à l’avanceAvisLe problème est que cette stratégie équivaut à un travail de trading manuel. Elle n’offre une certaine opportunité de profit qu’après avoir trouvé deux paires de trading appropriées, et la durée de vie du profit de la paire de trading peut être courte. Lorsqu’il s’avère qu’il n’y a aucune opportunité de profit, il est nécessaire d’arrêter la stratégie à temps pour éviter une baisse des bénéfices, voire une perte.
Les contrats TRUMP et MELANIA sont émis par la même équipe émettrice et disposent des mêmes fonds de contrôle, de sorte que leurs tendances de prix sont la plupart du temps très synchronisées. Cependant, en raison de facteurs tels que la conception du contrat ou l’exécution du marché, le prix de MELANIA a tendance à être inférieur de 1 à 2 secondes à celui de TRUMP. Ce petit délai offre aux arbitragistes la possibilité de capturer les différences de prix et d’effectuer des transactions de copie à haute fréquence. En d’autres termes, lorsque TRUMP fluctue rapidement, MELANIA a tendance à suivre le mouvement très rapidement. En profitant de ce délai, les transactions peuvent être réalisées avec un risque moindre.


Des phénomènes de corrélation similaires ne sont pas rares sur le marché des crypto-monnaies :
Cette corrélation fournit aux traders à haute fréquence et aux arbitragistes des signaux de trading stables et des opportunités opérationnelles à faible risque, mais elle nécessite également que les stratégies de trading soient très sensibles aux changements subtils du marché et soient capables de réagir en temps réel.
Le code se compose principalement de plusieurs parties, chaque module correspond aux étapes clés de la stratégie d’arbitrage.
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); // 取消挂单
}
}
}
La fonction principale utilise une boucle infinie pour exécuter en continu les étapes suivantes :
Acquisition de données et calcul de marché
Chaque cycle commence parexchange.GetRecords Obtenez les données de marché de Pair_A et Pair_B respectivement.
Déterminer les conditions d’ouverture et passer commande
Lorsqu’il n’y a pas de position actuelle (position_B.amount == 0) et que le trading est autorisé (afterTrade==1) :
Logique stop-profit et stop-loss
Une fois la position établie, la stratégie définira les ordres take-profit et stop-loss correspondants en fonction de la direction de la position :
Statistiques de profit et enregistrements de journal après la fermeture d’une position
Après la fermeture de chaque position, le système obtiendra les variations de la valeur nette du compte et comptera le nombre de bénéfices, le nombre de pertes et le montant cumulé des bénéfices/pertes.
Dans le même temps, des tableaux et des graphiques sont utilisés pour afficher les informations de position actuelles, les statistiques de transaction et les retards de cycle en temps réel, ce qui est pratique pour l’analyse ultérieure des effets de la stratégie.
Bien que cette stratégie exploite le délai subtil entre deux contrats hautement corrélés, de nombreux domaines peuvent encore être améliorés :
Cet article présente en détail les principes de base et le code d’implémentation d’une stratégie d’arbitrage de corrélation de contrats à court terme. De l’exploitation de la différence entre les hausses et les baisses de prix à la capture d’opportunités d’entrée, en passant par la définition de stop-profit et de stop-loss pour la gestion des positions, cette stratégie tire pleinement parti de la forte corrélation entre les actifs du marché des crypto-monnaies. Dans le même temps, nous avons également proposé un certain nombre de suggestions d’optimisation, notamment l’ajustement dynamique des paramètres, le filtrage du signal, la robustesse du système et l’optimisation du code, afin d’améliorer encore la stabilité et la rentabilité de la stratégie dans les applications en temps réel.
Bien que la stratégie soit particulièrement inspirée et simple à mettre en œuvre, toute opération d’arbitrage doit être traitée avec prudence sur le marché des crypto-monnaies à haute fréquence et volatil. J’espère que cet article pourra fournir une référence précieuse et une inspiration aux amis passionnés par le trading quantitatif et les stratégies d’arbitrage.
Remarque : l’environnement de test de stratégie est la simulation de trading OKX, et les détails spécifiques peuvent être modifiés pour différentes bourses
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);
}
}