avatar of 发明者量化-小小梦 发明者量化-小小梦
konzentrieren Sie sich auf Private Nachricht
4
konzentrieren Sie sich auf
1271
Anhänger

Interperioden-Arbitrage-Strategie für Kryptowährungen basierend auf Bollinger-Bändern

Erstellt in: 2020-02-22 18:54:23, aktualisiert am: 2023-10-10 21:11:20
comments   0
hits   4202

Interperioden-Arbitrage-Strategie für Kryptowährungen basierend auf Bollinger-Bändern

I. Zusammenfassung

In seinem 1987 erschienenen Buch The Alchemy of Finance stellte Soros einmal eine wichtige These auf: Ich glaube, dass die Marktpreise immer falsch sind, in dem Sinne, dass sie eine verzerrte Sicht der Zukunft darstellen. Die Hypothese effizienter Märkte ist nur eine Theorie. Tatsächlich sind Marktteilnehmer nicht immer rational und es ist ihnen zu jedem Zeitpunkt unmöglich, alle Informationen vollständig zu erhalten und objektiv zu interpretieren. Darüber hinaus ist das Feedback jedes Einzelnen unterschiedlich, auch wenn die Informationen gleich sind. Variieren. Mit anderen Worten: Im Preis selbst sind die fehlerhaften Erwartungen der Marktteilnehmer bereits enthalten, sodass die Marktpreise ihrem Wesen nach immer falsch sind. Dies kann für Arbitrageure eine Gewinnquelle sein.

2. Strategieprinzipien

Aufgrund der oben genannten Grundsätze wissen wir, dass in einem ineffizienten Terminmarkt die Marktauswirkungen auf Lieferverträge in verschiedenen Zeiträumen nicht immer synchron erfolgen und ihre Preisgestaltung nicht völlig effektiv ist. Wenn dann auf der Grundlage der Liefervertragspreise desselben Handelsobjekts in verschiedenen Zeiträumen ein großer Preisunterschied zwischen den beiden Preisen besteht, können Sie Terminkontrakte verschiedener Zeiträume gleichzeitig kaufen und verkaufen, um zeitraumübergreifende Arbitrage durchzuführen. Mit digitalen Währungen ist ebenso wie mit Rohstoff-Futures eine Kombination periodenübergreifender Arbitrageverträge verbunden. An der OkEX-Börse gibt es beispielsweise: ETC Weekly, ETC Biweekly und ETC Quarterly.

Angenommen, der Preisunterschied zwischen dem wöchentlichen ETC und dem vierteljährlichen ETC bleibt für lange Zeit bei etwa 5. Wenn der Spread an einem bestimmten Tag 7 erreicht, erwarten wir, dass der Spread irgendwann in der Zukunft wieder auf 5 zurückkehrt. Dann können Sie ETC wöchentlich verkaufen und ETC vierteljährlich kaufen, um den Spread zu verkürzen. umgekehrt. Obwohl dieser Preisunterschied besteht, ist manuelle Arbitrage aufgrund der zeitaufwändigen manuellen Vorgehensweise, der mangelnden Genauigkeit und der Auswirkungen von Preisänderungen häufig mit zahlreichen Unsicherheiten verbunden. Der Reiz der quantitativen Arbitrage liegt darin, Arbitragemöglichkeiten durch quantitative Modelle zu erfassen und Arbitrage-Handelsstrategien zu formulieren sowie durch programmierte Algorithmen automatisch Handelsaufträge an Börsen zu erteilen, um so Möglichkeiten schnell und präzise zu erfassen und effizient und stabil Gewinne zu erzielen.

3. Strategielogik

In diesem Artikel erfahren Sie, wie Sie die Inventor Quantitative Trading Platform und den ETC-Futures-Kontrakt an der OkEX-Börse im digitalen Devisenhandel nutzen. Dabei wird eine einfache Arbitrage-Strategie verwendet, um zu demonstrieren, wie Sie sofortige Arbitrage-Gelegenheiten nutzen und jede Gelegenheit nutzen können, um Gewinne zu erzielen, während Sie sich absichern mögliche Risiken.

Erstellen einer Arbitragestrategie für Kryptowährungen über mehrere Perioden hinweg Schwierigkeit: Normal

Strategisches Umfeld

  • Gegenstand der Transaktion: Ethereum Classic (ETC)
  • Preisdifferenzdaten: ETC wöchentlich – ETC vierteljährlich (Kointegrationstest ausgelassen)
  • Handelszyklus: 5 Minuten
  • Positionsübereinstimmung: 1:1
  • Transaktionstyp: Gleiches Produkt über mehrere Perioden hinweg

Strategielogik

  • Bedingungen für die Eröffnung eines langen Spreads: Wenn das Girokonto keine Positionen aufweist und der Spread kleiner als der untere Boll Track ist, gehen Sie eine Long-Position auf den Spread ein. Das heißt: Kaufen Sie ETC für die Woche und verkaufen Sie ETC für das Quartal.
  • Bedingungen für das Eröffnen einer Short-Spread-Position: Wenn das Girokonto keine Positionen aufweist und der Spread größer als der obere Boll-Track ist, shorten Sie den Spread. Das heißt: ETC für die Woche verkaufen und ETC für das Quartal kaufen.
  • Bedingungen zum Schließen eines Long Spreads: Wenn das Girokonto eine wöchentliche Long-Position in ETC und eine vierteljährliche Short-Position in ETC hält und der Spread größer als der mittlere Track von Boll ist, wird der Long Spread geschlossen. Das heißt: Verkaufen Sie ETC für die Woche und kaufen Sie ETC für das Quartal.
  • Bedingungen zum Schließen eines Short Spreads: Wenn das Girokonto diese Woche eine Short-Position für ETC und im Quartal eine Long-Position für ETC hält und der Spread kleiner als der mittlere Boll-Track ist, wird der Short Spread geschlossen. Das heißt: Kaufen Sie ETC für die Woche und verkaufen Sie ETC für das Quartal.

4. Schreiben Sie einen politischen Rahmen

Das Obige ist eine einfache Beschreibung der Logik der Arbitragestrategie für digitale Währungen über mehrere Perioden hinweg. Wie setzen Sie also Ihre Ideen im Programm um? Wir haben zunächst versucht, das Framework auf der Inventor Quantitative Trading Platform aufzubauen.

function Data() {}  // 基础数据函数
Data.prototype.mp = function () {}  // 持仓函数
Data.prototype.boll = function () {}  // 指标函数
Data.prototype.trade = function () {}  // 下单函数
Data.prototype.cancelOrders = function () {}  // 撤单函数
Data.prototype.isEven = function () {}  // 处理单只合约函数
Data.prototype.drawingChart = function () {}  // 画图函数

// 交易条件
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB);  // 创建一个基础数据对象
    var accountStocks = data.accountData.Stocks;  // 账户余额
    var boll = data.boll(dataLength, timeCycle);  // 计算boll技术指标
    data.trade();  // 计算交易条件下单
    data.cancelOrders();  // 撤单
    data.drawingChart(boll);  // 画图
    data.isEven();  // 处理持有单个合约
}

//入口函数
function main() {
    while (true) {  // 进入轮询模式
        onTick();  // 执行onTick函数
        Sleep(500);  // 休眠0.5秒
    }
}

5. Schreiben Sie eine Strategie

Durch den Vergleich der strategischen Ideen und Handelsprozesse können Sie ganz einfach einen Strategierahmen erstellen. Die gesamte Strategie kann in drei Schritte vereinfacht werden:

  • Verarbeitung vor der Transaktion.
  • Daten abrufen und berechnen.
  • Geben Sie eine Bestellung auf und kümmern Sie sich um die Nachverfolgung.

Als Nächstes müssen wir basierend auf dem tatsächlichen Transaktionsprozess und den Transaktionsdetails den erforderlichen Detailcode in das Strategie-Framework einfügen.

Vortransaktionsverarbeitung Schritt 1: Deklarieren Sie in der globalen Umgebung die erforderlichen globalen Variablen.

//声明一个配置图表的 chart 对象
var chart = { }

//调用 Chart 函数,初始化图表
var ObjChart = Chart ( chart )

//声明一个空数组,用来存储价差序列
var bars = [ ]

//声明一个记录历史数据时间戳变量
var oldTime = 0

Schritt 2: Konfigurieren Sie die externen Parameter der Strategie.

// 参数
var tradeTypeA = "this_week"; // 套利A合约
var tradeTypeB = "quarter"; // 套利B合约
var dataLength = 10; //指标周期长度
var timeCycle = 1; // K线周期
var name = "ETC"; // 币种
var unit = 1; // 下单量

Schritt 3: Datenverarbeitungsfunktionen definieren Basisdatenfunktion: Daten ( ) Erstellen Sie einen Konstruktor „Data“ und definieren Sie seine internen Eigenschaften. Enthält: Kontodaten, Positionsdaten, Zeitstempel der K-Line-Daten, Geld-/Briefkurs des Arbitrage-A/B-Vertrags und Forward-/Reverse-Arbitrage-Spreads.

// 基础数据
function Data(tradeTypeA, tradeTypeB) { // 传入套利A合约和套利B合约
    this.accountData = _C(exchange.GetAccount); // 获取账户信息
    this.positionData = _C(exchange.GetPosition); // 获取持仓信息
    var recordsData = _C(exchange.GetRecords); //获取K线数据
    exchange.SetContractType(tradeTypeA); // 订阅套利A合约
    var depthDataA = _C(exchange.GetDepth); // 套利A合约深度数据
    exchange.SetContractType(tradeTypeB); // 订阅套利B合约
    var depthDataB = _C(exchange.GetDepth); // 套利B合约深度数据
    this.time = recordsData[recordsData.length - 1].Time; // 获取最新数据时间
    this.askA = depthDataA.Asks[0].Price; // 套利A合约卖一价
    this.bidA = depthDataA.Bids[0].Price; // 套利A合约买一价
    this.askB = depthDataB.Asks[0].Price; // 套利B合约卖一价
    this.bidB = depthDataB.Bids[0].Price; // 套利B合约买一价
    // 正套价差(合约A卖一价 - 合约B买一价)
    this.basb = depthDataA.Asks[0].Price - depthDataB.Bids[0].Price;
    // 反套价差(合约A买一价 - 合约B卖一价)
    this.sabb = depthDataA.Bids[0].Price - depthDataB.Asks[0].Price;
}

Positionsfunktion abrufen: mp ( ) Durchlaufen Sie das gesamte Positionsarray und geben Sie die Anzahl der Positionen des angegebenen Vertrags und der angegebenen Richtung zurück. Wenn keine vorhanden sind, geben Sie false zurück.

// 获取持仓
Data.prototype.mp = function (tradeType, type) {
    var positionData = this.positionData; // 获取持仓信息
    for (var i = 0; i < positionData.length; i++) {
        if (positionData[i].ContractType == tradeType) {
            if (positionData[i].Type == type) {
                if (positionData[i].Amount > 0) {
                    return positionData[i].Amount;
                }
            }
        }
    }
    return false;
}

K-Linie und Indikatorfunktionen: boll ( ) Synthetisieren Sie eine neue K-Line-Sequenz basierend auf den Forward/Reverse-Arbitrage-Spread-Daten. Und gibt die vom Boll-Indikator berechneten Daten zur oberen, mittleren und unteren Schiene zurück.

// 合成新K线数据和boll指标数据
Data.prototype.boll = function (num, timeCycle) {
    var self = {}; // 临时对象
    // 正套价差和反套价差中间值
    self.Close = (this.basb + this.sabb) / 2;
    if (this.timeA == this.timeB) {
        self.Time = this.time;
    } // 对比两个深度数据时间戳
    if (this.time - oldTime > timeCycle * 60000) {
        bars.push(self);
        oldTime = this.time;
    } // 根据指定时间周期,在K线数组里面传入价差数据对象
    if (bars.length > num * 2) {
        bars.shift(); // 控制K线数组长度
    } else {
        return;
    }
    var boll = TA.BOLL(bars, num, 2); // 调用talib库中的boll指标
    return {
        up: boll[0][boll[0].length - 1], // boll指标上轨
        middle: boll[1][boll[1].length - 1], // boll指标中轨
        down: boll[2][boll[2].length - 1] // boll指标下轨
    } // 返回一个处理好的boll指标数据
}

Orderfunktion: Handel ( ) Geben Sie den Auftragsvertragsnamen und die Auftragsart ein, erteilen Sie anschließend eine Bestellung zum Gegenwertpreis und geben Sie das Ergebnis nach der Auftragserteilung zurück. Da es notwendig ist, gleichzeitig zwei Orders in unterschiedliche Richtungen zu platzieren, wird der Kauf-/Verkaufspreis innerhalb der Funktion entsprechend dem Orderkontraktnamen umgerechnet.

// 下单
Data.prototype.trade = function (tradeType, type) {
    exchange.SetContractType(tradeType); // 下单前先重新订阅合约
    var askPrice, bidPrice;
    if (tradeType == tradeTypeA) { // 如果是A合约下单
        askPrice = this.askA; // 设置askPrice
        bidPrice = this.bidA; // 设置bidPrice
    } else if (tradeType == tradeTypeB) { // 如果是B合约下单
        askPrice = this.askB; // 设置askPrice
        bidPrice = this.bidB; // 设置bidPrice
    }
    switch (type) { // 匹配下单模式
        case "buy":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Buy(askPrice, unit);
        case "sell":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Sell(bidPrice, unit);
        case "closebuy":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Sell(bidPrice, unit);
        case "closesell":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Buy(askPrice, unit);
        default:
            return false;
    }
}

Funktion zum Stornieren von Bestellungen: cancelOrders () Erhalten Sie eine Übersicht aller nicht erfüllten Bestellungen und stornieren Sie diese nacheinander. Wenn eine nicht erfüllte Bestellung vorliegt, wird „Falsch“ zurückgegeben, und wenn keine nicht erfüllte Bestellung vorliegt, wird „Wahr“ zurückgegeben.

// 取消订单
Data.prototype.cancelOrders = function () {
    Sleep(500); // 撤单前先延时,因为有些交易所你懂的
    var orders = _C(exchange.GetOrders); // 获取未成交订单数组
    if (orders.length > 0) { // 如果有未成交的订单
        for (var i = 0; i < orders.length; i++) { //遍历未成交订单数组
            exchange.CancelOrder(orders[i].Id); //逐个取消未成交的订单
            Sleep(500); //延时0.5秒
        }
        return false; // 如果取消了未成交的单子就返回false
    }
    return true; //如果没有未成交的订单就返回true
}

Handhabung des Haltens eines einzelnen Vertrags: isEven() Wenn wir es beim Arbitragehandel mit einer Single-Leg-Situation zu tun haben, schließen wir einfach alle Positionen, um das Problem zu lösen. Selbstverständlich ist auch eine Umstellung auf eine Folgebestellart möglich.

// 处理持有单个合约
Data.prototype.isEven = function () {
    var positionData = this.positionData; // 获取持仓信息
    var type = null; // 转换持仓方向
    // 如果持仓数组长度余2不等于0或者持仓数组长度不等于2
    if (positionData.length % 2 != 0 || positionData.length != 2) {
        for (var i = 0; i < positionData.length; i++) { // 遍历持仓数组
            if (positionData[i].Type == 0) { // 如果是多单
                type = 10; // 设置下单参数
            } else if (positionData[i].Type == 1) { // 如果是空单
                type = -10; // 设置下单参数
            }
            // 平掉所有仓位
            this.trade(positionData[i].ContractType, type, positionData[i].Amount);
        }
    }
}

Zeichenfunktion: drawingChart ( ) Rufen Sie die Methode ObjChart.add() auf, um die erforderlichen Marktdaten und Indikatordaten in das Diagramm einzuzeichnen: obere Spur, mittlere Spur, untere Spur und positiver/negativer Spread.

// 画图
Data.prototype.drawingChart = function (boll) {
    var nowTime = new Date().getTime();
    ObjChart.add([0, [nowTime, boll.up]]);
    ObjChart.add([1, [nowTime, boll.middle]]);
    ObjChart.add([2, [nowTime, boll.down]]);
    ObjChart.add([3, [nowTime, this.basb]]);
    ObjChart.add([4, [nowTime, this.sabb]]);
    ObjChart.update(chart);
}

Schritt 4: Führen Sie in der Eingabefunktion main() den Vorverarbeitungscode vor der Transaktion aus. Dieser Code wird nach dem Programmstart nur einmal ausgeführt. enthalten:

  • Filtern Sie weniger wichtige Nachrichten in der Konsole SetErrorFilter ()
  • Stellen Sie die zu handelnde digitale Währung ein exchange.IO ( )
  • Löschen Sie das zuvor gezeichnete Diagramm, bevor das Programm startet ObjChart.reset ( )
  • Löschen der vorherigen Statusleisteninformationen vor dem Programmstart LogProfitReset ( )
//入口函数
function main() {
    // 过滤控制台中不是很重要的信息
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //设置要交易的数字货币币种
    ObjChart.reset(); //程序启动前清空之前绘制的图表
    LogProfitReset(); //程序启动前清空之前的状态栏信息
}

Nachdem wir die oben beschriebene Vorverarbeitung vor dem Handel definiert haben, fahren wir mit dem nächsten Schritt fort, wechseln in den Polling-Modus und führen die Funktion onTick() wiederholt aus. Und legen Sie die Ruhezeit fest, wenn Sleep() eine Abfrage durchführt, da einige APIs für den Kryptowährungsaustausch über integrierte Zugriffsbeschränkungen innerhalb eines bestimmten Zeitraums verfügen.

//入口函数
function main() {
    // 过滤控制台中不是很重要的信息
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //设置要交易的数字货币币种
    ObjChart.reset(); //程序启动前清空之前绘制的图表
    LogProfitReset(); //程序启动前清空之前的状态栏信息
    while (true) { // 进入轮询模式
        onTick(); // 执行onTick函数
        Sleep(500); // 休眠0.5秒
    }
}

Daten abrufen und berechnen Schritt 1: Holen Sie sich grundlegende Datenobjekte, Kontostände und Boll-Indikatordaten zur Verwendung in der Handelslogik.

// 交易条件
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // 创建一个基础数据对象
    var accountStocks = data.accountData.Stocks; // 账户余额
    var boll = data.boll(dataLength, timeCycle); // 获取boll指标数据
    if (!boll) return; // 如果没有boll数据就返回
}

Eine Bestellung aufgeben und nachverfolgen Schritt 1: Führen Sie Kauf- und Verkaufsoperationen gemäß der oben genannten Strategielogik durch. Zunächst wird ermittelt, ob die Preis- und Indikatorbedingungen erfüllt sind, dann wird ermittelt, ob die Positionsbedingungen erfüllt sind, und schließlich wird die Handelsauftragsfunktion () ausgeführt.

// 交易条件
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // 创建一个基础数据对象
    var accountStocks = data.accountData.Stocks; // 账户余额
    var boll = data.boll(dataLength, timeCycle); // 获取boll指标数据
    if (!boll) return; // 如果没有boll数据就返回
    // 价差说明
    // basb = (合约A卖一价 - 合约B买一价)
    // sabb = (合约A买一价 - 合约B卖一价)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // 如果sabb高于中轨
        if (data.mp(tradeTypeA, 0)) { // 下单前检测合约A是否有多单
            data.trade(tradeTypeA, "closebuy"); // 合约A平多
        }
        if (data.mp(tradeTypeB, 1)) { // 下单前检测合约B是否有空单
            data.trade(tradeTypeB, "closesell"); // 合约B平空
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // 如果basb低于中轨
        if (data.mp(tradeTypeA, 1)) { // 下单前检测合约A是否有空单
            data.trade(tradeTypeA, "closesell"); // 合约A平空
        }
        if (data.mp(tradeTypeB, 0)) { // 下单前检测合约B是否有多单
            data.trade(tradeTypeB, "closebuy"); // 合约B平多
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // 如果账户有余额
        if (data.basb < boll.down) { // 如果basb价差低于下轨
            if (!data.mp(tradeTypeA, 0)) { // 下单前检测合约A是否有多单
                data.trade(tradeTypeA, "buy"); // 合约A开多
            }
            if (!data.mp(tradeTypeB, 1)) { // 下单前检测合约B是否有空单
                data.trade(tradeTypeB, "sell"); // 合约B开空
            }
        } else if (data.sabb > boll.up) { // 如果sabb价差高于上轨
            if (!data.mp(tradeTypeA, 1)) { // 下单前检测合约A是否有空单
                data.trade(tradeTypeA, "sell"); // 合约A开空
            }
            if (!data.mp(tradeTypeB, 0)) { // 下单前检测合约B是否有多单
                data.trade(tradeTypeB, "buy"); // 合约B开多
            }
        }
    }
}

Schritt 2: Nachdem die Bestellung aufgegeben wurde, müssen ungewöhnliche Situationen wie nicht erfüllte Bestellungen und das Halten eines einzelnen Vertrags behandelt werden. und Diagramme zeichnen.

// 交易条件
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // 创建一个基础数据对象
    var accountStocks = data.accountData.Stocks; // 账户余额
    var boll = data.boll(dataLength, timeCycle); // 获取boll指标数据
    if (!boll) return; // 如果没有boll数据就返回
    // 价差说明
    // basb = (合约A卖一价 - 合约B买一价)
    // sabb = (合约A买一价 - 合约B卖一价)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // 如果sabb高于中轨
        if (data.mp(tradeTypeA, 0)) { // 下单前检测合约A是否有多单
            data.trade(tradeTypeA, "closebuy"); // 合约A平多
        }
        if (data.mp(tradeTypeB, 1)) { // 下单前检测合约B是否有空单
            data.trade(tradeTypeB, "closesell"); // 合约B平空
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // 如果basb低于中轨
        if (data.mp(tradeTypeA, 1)) { // 下单前检测合约A是否有空单
            data.trade(tradeTypeA, "closesell"); // 合约A平空
        }
        if (data.mp(tradeTypeB, 0)) { // 下单前检测合约B是否有多单
            data.trade(tradeTypeB, "closebuy"); // 合约B平多
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // 如果账户有余额
        if (data.basb < boll.down) { // 如果basb价差低于下轨
            if (!data.mp(tradeTypeA, 0)) { // 下单前检测合约A是否有多单
                data.trade(tradeTypeA, "buy"); // 合约A开多
            }
            if (!data.mp(tradeTypeB, 1)) { // 下单前检测合约B是否有空单
                data.trade(tradeTypeB, "sell"); // 合约B开空
            }
        } else if (data.sabb > boll.up) { // 如果sabb价差高于上轨
            if (!data.mp(tradeTypeA, 1)) { // 下单前检测合约A是否有空单
                data.trade(tradeTypeA, "sell"); // 合约A开空
            }
            if (!data.mp(tradeTypeB, 0)) { // 下单前检测合约B是否有多单
                data.trade(tradeTypeB, "buy"); // 合约B开多
            }
        }
    }
    data.cancelOrders(); // 撤单
    data.drawingChart(boll); // 画图
    data.isEven(); // 处理持有单个合约
}

6. Komplette Strategie

Oben haben wir in etwas mehr als 200 Zeilen eine einfache Interperioden-Arbitragestrategie für digitale Währungen erstellt. Der vollständige Code lautet wie folgt:

// 全局变量
// 声明一个配置图表的 chart 对象
var chart = {
    __isStock: true,
    tooltip: {
        xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
    },
    title: {
        text: '交易盈亏曲线图(详细)'
    },
    rangeSelector: {
        buttons: [{
            type: 'hour',
            count: 1,
            text: '1h'
        }, {
            type: 'hour',
            count: 2,
            text: '3h'
        }, {
            type: 'hour',
            count: 8,
            text: '8h'
        }, {
            type: 'all',
            text: 'All'
        }],
        selected: 0,
        inputEnabled: false
    },
    xAxis: {
        type: 'datetime'
    },
    yAxis: {
        title: {
            text: '价差'
        },
        opposite: false,
    },
    series: [{
        name: "上轨",
        id: "线1,up",
        data: []
    }, {
        name: "中轨",
        id: "线2,middle",
        data: []
    }, {
        name: "下轨",
        id: "线3,down",
        data: []
    }, {
        name: "basb",
        id: "线4,basb",
        data: []
    }, {
        name: "sabb",
        id: "线5,sabb",
        data: []
    }]
};
var ObjChart = Chart(chart); // 画图对象
var bars = []; // 存储价差序列
var oldTime = 0; // 记录历史数据时间戳

// 参数
var tradeTypeA = "this_week"; // 套利A合约
var tradeTypeB = "quarter"; // 套利B合约
var dataLength = 10; //指标周期长度
var timeCycle = 1; // K线周期
var name = "ETC"; // 币种
var unit = 1; // 下单量

// 基础数据
function Data(tradeTypeA, tradeTypeB) { // 传入套利A合约和套利B合约
    this.accountData = _C(exchange.GetAccount); // 获取账户信息
    this.positionData = _C(exchange.GetPosition); // 获取持仓信息
    var recordsData = _C(exchange.GetRecords); //获取K线数据
    exchange.SetContractType(tradeTypeA); // 订阅套利A合约
    var depthDataA = _C(exchange.GetDepth); // 套利A合约深度数据
    exchange.SetContractType(tradeTypeB); // 订阅套利B合约
    var depthDataB = _C(exchange.GetDepth); // 套利B合约深度数据
    this.time = recordsData[recordsData.length - 1].Time; // 获取最新数据时间
    this.askA = depthDataA.Asks[0].Price; // 套利A合约卖一价
    this.bidA = depthDataA.Bids[0].Price; // 套利A合约买一价
    this.askB = depthDataB.Asks[0].Price; // 套利B合约卖一价
    this.bidB = depthDataB.Bids[0].Price; // 套利B合约买一价
    // 正套价差(合约A卖一价 - 合约B买一价)
    this.basb = depthDataA.Asks[0].Price - depthDataB.Bids[0].Price;
    // 反套价差(合约A买一价 - 合约B卖一价)
    this.sabb = depthDataA.Bids[0].Price - depthDataB.Asks[0].Price;
}

// 获取持仓
Data.prototype.mp = function (tradeType, type) {
    var positionData = this.positionData; // 获取持仓信息
    for (var i = 0; i < positionData.length; i++) {
        if (positionData[i].ContractType == tradeType) {
            if (positionData[i].Type == type) {
                if (positionData[i].Amount > 0) {
                    return positionData[i].Amount;
                }
            }
        }
    }
    return false;
}

// 合成新K线数据和boll指标数据
Data.prototype.boll = function (num, timeCycle) {
    var self = {}; // 临时对象
    // 正套价差和反套价差中间值
    self.Close = (this.basb + this.sabb) / 2;
    if (this.timeA == this.timeB) {
        self.Time = this.time;
    } // 对比两个深度数据时间戳
    if (this.time - oldTime > timeCycle * 60000) {
        bars.push(self);
        oldTime = this.time;
    } // 根据指定时间周期,在K线数组里面传入价差数据对象
    if (bars.length > num * 2) {
        bars.shift(); // 控制K线数组长度
    } else {
        return;
    }
    var boll = TA.BOLL(bars, num, 2); // 调用talib库中的boll指标
    return {
        up: boll[0][boll[0].length - 1], // boll指标上轨
        middle: boll[1][boll[1].length - 1], // boll指标中轨
        down: boll[2][boll[2].length - 1] // boll指标下轨
    } // 返回一个处理好的boll指标数据
}

// 下单
Data.prototype.trade = function (tradeType, type) {
    exchange.SetContractType(tradeType); // 下单前先重新订阅合约
    var askPrice, bidPrice;
    if (tradeType == tradeTypeA) { // 如果是A合约下单
        askPrice = this.askA; // 设置askPrice
        bidPrice = this.bidA; // 设置bidPrice
    } else if (tradeType == tradeTypeB) { // 如果是B合约下单
        askPrice = this.askB; // 设置askPrice
        bidPrice = this.bidB; // 设置bidPrice
    }
    switch (type) { // 匹配下单模式
        case "buy":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Buy(askPrice, unit);
        case "sell":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Sell(bidPrice, unit);
        case "closebuy":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Sell(bidPrice, unit);
        case "closesell":
            exchange.SetDirection(type); // 设置下单模式
            return exchange.Buy(askPrice, unit);
        default:
            return false;
    }
}

// 取消订单
Data.prototype.cancelOrders = function () {
    Sleep(500); // 撤单前先延时,因为有些交易所你懂的
    var orders = _C(exchange.GetOrders); // 获取未成交订单数组
    if (orders.length > 0) { // 如果有未成交的订单
        for (var i = 0; i < orders.length; i++) { //遍历未成交订单数组
            exchange.CancelOrder(orders[i].Id); //逐个取消未成交的订单
            Sleep(500); //延时0.5秒
        }
        return false; // 如果取消了未成交的单子就返回false
    }
    return true; //如果没有未成交的订单就返回true
}

// 处理持有单个合约
Data.prototype.isEven = function () {
    var positionData = this.positionData; // 获取持仓信息
    var type = null; // 转换持仓方向
    // 如果持仓数组长度余2不等于0或者持仓数组长度不等于2
    if (positionData.length % 2 != 0 || positionData.length != 2) {
        for (var i = 0; i < positionData.length; i++) { // 遍历持仓数组
            if (positionData[i].Type == 0) { // 如果是多单
                type = 10; // 设置下单参数
            } else if (positionData[i].Type == 1) { // 如果是空单
                type = -10; // 设置下单参数
            }
            // 平掉所有仓位
            this.trade(positionData[i].ContractType, type, positionData[i].Amount);
        }
    }
}

// 画图
Data.prototype.drawingChart = function (boll) {
    var nowTime = new Date().getTime();
    ObjChart.add([0, [nowTime, boll.up]]);
    ObjChart.add([1, [nowTime, boll.middle]]);
    ObjChart.add([2, [nowTime, boll.down]]);
    ObjChart.add([3, [nowTime, this.basb]]);
    ObjChart.add([4, [nowTime, this.sabb]]);
    ObjChart.update(chart);
}

// 交易条件
function onTick() {
    var data = new Data(tradeTypeA, tradeTypeB); // 创建一个基础数据对象
    var accountStocks = data.accountData.Stocks; // 账户余额
    var boll = data.boll(dataLength, timeCycle); // 获取boll指标数据
    if (!boll) return; // 如果没有boll数据就返回
    // 价差说明
    // basb = (合约A卖一价 - 合约B买一价)
    // sabb = (合约A买一价 - 合约B卖一价)
    if (data.sabb > boll.middle && data.sabb < boll.up) { // 如果sabb高于中轨
        if (data.mp(tradeTypeA, 0)) { // 下单前检测合约A是否有多单
            data.trade(tradeTypeA, "closebuy"); // 合约A平多
        }
        if (data.mp(tradeTypeB, 1)) { // 下单前检测合约B是否有空单
            data.trade(tradeTypeB, "closesell"); // 合约B平空
        }
    } else if (data.basb < boll.middle && data.basb > boll.down) { // 如果basb低于中轨
        if (data.mp(tradeTypeA, 1)) { // 下单前检测合约A是否有空单
            data.trade(tradeTypeA, "closesell"); // 合约A平空
        }
        if (data.mp(tradeTypeB, 0)) { // 下单前检测合约B是否有多单
            data.trade(tradeTypeB, "closebuy"); // 合约B平多
        }
    }
    if (accountStocks * Math.max(data.askA, data.askB) > 1) { // 如果账户有余额
        if (data.basb < boll.down) { // 如果basb价差低于下轨
            if (!data.mp(tradeTypeA, 0)) { // 下单前检测合约A是否有多单
                data.trade(tradeTypeA, "buy"); // 合约A开多
            }
            if (!data.mp(tradeTypeB, 1)) { // 下单前检测合约B是否有空单
                data.trade(tradeTypeB, "sell"); // 合约B开空
            }
        } else if (data.sabb > boll.up) { // 如果sabb价差高于上轨
            if (!data.mp(tradeTypeA, 1)) { // 下单前检测合约A是否有空单
                data.trade(tradeTypeA, "sell"); // 合约A开空
            }
            if (!data.mp(tradeTypeB, 0)) { // 下单前检测合约B是否有多单
                data.trade(tradeTypeB, "buy"); // 合约B开多
            }
        }
    }
    data.cancelOrders(); // 撤单
    data.drawingChart(boll); // 画图
    data.isEven(); // 处理持有单个合约
}

//入口函数
function main() {
    // 过滤控制台中不是很重要的信息
    SetErrorFilter("429|GetRecords:|GetOrders:|GetDepth:|GetAccount|:Buy|Sell|timeout|Futures_OP");
    exchange.IO("currency", name + '_USDT'); //设置要交易的数字货币币种
    ObjChart.reset(); //程序启动前清空之前绘制的图表
    LogProfitReset(); //程序启动前清空之前的状态栏信息
    while (true) { // 进入轮询模式
        onTick(); // 执行onTick函数
        Sleep(500); // 休眠0.5秒
    }
}

Richtlinienadresse: https://www.fmz.com/strategy/104964

VII. Fazit

Diese Strategie ist nur ein Ausgangspunkt. Der eigentliche Handel ist nicht so einfach, aber Sie können die Beispiele nutzen, um Ihrer Fantasie freien Lauf zu lassen. Ich muss alle daran erinnern, dass sich reine Perioden-Arbitrage-Strategien angesichts meiner begrenzten Erfahrung angesichts des aktuellen Zustands des digitalen Währungsmarktes grundsätzlich nicht lohnen, unabhängig davon, ob es sich um risikofreie Dreiecksarbitrage oder Cross-Market-Arbitrage handelt. .

Der Grund hierfür ist, dass die Marge, unabhängig vom Terminmarkt der digitalen Währungsbörse, kein gesetzliches Zahlungsmittel ist. Heutzutage sind fast alle digitalen Währungen seit Jahresbeginn um etwa 70 % gefallen. Mit anderen Worten: Die Strategie bringt immer Geld, aber der Preis der Währung fällt. Wenn man sich umschaut, scheint sich der Markt für digitale Währungen von der Blockchain abgekoppelt zu haben. Genau wie bei den Tulpen damals basieren die Preise immer auf den Erwartungen und dem Vertrauen der Menschen, und das Vertrauen basiert auf den Preisen …