
戦略設計の初心者にとって、ヘッジ戦略は非常に優れたトレーニング戦略です。この記事では、初心者が設計経験を積むことができるように、シンプルだが現実的なデジタル通貨スポットヘッジ戦略を実装します。
まず、設計すべき戦略はデジタル通貨のスポットヘッジ戦略であることは明らかです。私たちは、2つのスポット取引所のうち、価格の高い取引所で売り、価格の低い取引所で買うという最も単純なヘッジ戦略を設計します。利益を上げるために差額を受け取ります。より高い価格の取引所がすべてコイン建てである場合(より高い価格のコインがすべて売られるため)、およびより低い価格の取引所がすべてコイン建てである場合(より低い価格のコインがすべて買われるため)、ヘッジは不可能です。現時点では、価格が反転するのを待ってヘッジするしかありません。
ヘッジを行う場合、取引所は注文価格と数量に精度制限を設けており、最小注文数量制限もあります。最小制限に加えて、戦略では一度にヘッジするための最大注文量も考慮する必要があります。注文量が多すぎると、市場に十分な注文が出なくなります。また、2 つの取引所の通貨表示が異なる場合は、為替レートを使用して変換する方法も考慮する必要があります。ヘッジする場合、手数料と注文のスリッページはどちらも取引コストです。価格差がある限りヘッジすることはできません。そのため、価格差をヘッジするためのトリガー値もあります。価格差が一定のレベルを超えると、ヘッジすると損失が発生します。
これらの考慮事項に基づいて、戦略ではいくつかのパラメータを設計する必要があります。
hedgeDiffPrice価格差がこの値を超えると、ヘッジ操作がトリガーされます。minHedgeAmountヘッジできる最小注文数量(コイン単位)。maxHedgeAmount、1つのヘッジの最大注文数量(コイン数)。pricePrecisionA取引所Aの注文価格精度(小数点以下の桁数)。amountPrecisionA取引所Aの注文数量精度(小数点以下の桁数)。pricePrecisionB、取引所Bの注文価格精度(小数点以下の桁数)。amountPrecisionB、取引所Bの注文数量精度(小数点以下の桁数)。rateA最初に追加された交換オブジェクトの為替レート変換。デフォルト値は 1、変換なしです。rateB2 番目に追加された交換オブジェクトの為替レート変換では、デフォルト値は 1 であり、変換は実行されません。ヘッジ戦略では、2 つのアカウントのコインの数を変更しない (つまり、方向性のあるポジションを保持せず、中立性を維持する) 必要があるため、常にバランスをチェックするためのバランス ロジックが戦略に必要です。残高を確認する場合、2つの取引所から資産データを取得することが不可欠です。それを使用するには関数を記述する必要があります。
function updateAccs(arrEx) {
var ret = []
for (var i = 0 ; i < arrEx.length ; i++) {
var acc = arrEx[i].GetAccount()
if (!acc) {
return null
}
ret.push(acc)
}
return ret
}
注文が発注後に実行されない場合は、保留のままにせず、時間内にキャンセルする必要があります。この操作はバランスモジュールとヘッジロジックの両方で処理する必要があるため、完全な注文引き出し機能を設計する必要があります。
function cancelAll() {
_.each(exchanges, function(ex) {
while (true) {
var orders = _C(ex.GetOrders)
if (orders.length == 0) {
break
}
for (var i = 0 ; i < orders.length ; i++) {
ex.CancelOrder(orders[i].Id, orders[i])
Sleep(500)
}
}
})
}
コインの枚数のバランスをとる場合、ある一定の深さのデータに蓄積された一定枚数のコインの価格を見つける必要があるため、それを処理するこのような関数が必要になります。
function getDepthPrice(depth, side, amount) {
var arr = depth[side]
var sum = 0
var price = null
for (var i = 0 ; i < arr.length ; i++) {
var ele = arr[i]
sum += ele.Amount
if (sum >= amount) {
price = ele.Price
break
}
}
return price
}
次に、同時実行可能なように設計する必要がある特定のヘッジ注文操作を設計して記述する必要があります。
function hedge(buyEx, sellEx, price, amount) {
var buyRoutine = buyEx.Go("Buy", price, amount)
var sellRoutine = sellEx.Go("Sell", price, amount)
Sleep(500)
buyRoutine.wait()
sellRoutine.wait()
}
最後に、少し複雑なバランス機能の設計を完成させましょう。
keepBalance
function keepBalance(initAccs, nowAccs, depths) {
var initSumStocks = 0
var nowSumStocks = 0
_.each(initAccs, function(acc) {
initSumStocks += acc.Stocks + acc.FrozenStocks
})
_.each(nowAccs, function(acc) {
nowSumStocks += acc.Stocks + acc.FrozenStocks
})
var diff = nowSumStocks - initSumStocks
// 计算币差
if (Math.abs(diff) > minHedgeAmount && initAccs.length == nowAccs.length && nowAccs.length == depths.length) {
var index = -1
var available = []
var side = diff > 0 ? "Bids" : "Asks"
for (var i = 0 ; i < nowAccs.length ; i++) {
var price = getDepthPrice(depths[i], side, Math.abs(diff))
if (side == "Bids" && nowAccs[i].Stocks > Math.abs(diff)) {
available.push(i)
} else if (price && nowAccs[i].Balance / price > Math.abs(diff)) {
available.push(i)
}
}
for (var i = 0 ; i < available.length ; i++) {
if (index == -1) {
index = available[i]
} else {
var priceIndex = getDepthPrice(depths[index], side, Math.abs(diff))
var priceI = getDepthPrice(depths[available[i]], side, Math.abs(diff))
if (side == "Bids" && priceIndex && priceI && priceI > priceIndex) {
index = available[i]
} else if (priceIndex && priceI && priceI < priceIndex) {
index = available[i]
}
}
}
if (index == -1) {
Log("无法平衡")
} else {
// 平衡下单
var price = getDepthPrice(depths[index], side, Math.abs(diff))
if (price) {
var tradeFunc = side == "Bids" ? exchanges[index].Sell : exchanges[index].Buy
tradeFunc(price, Math.abs(diff))
} else {
Log("价格无效", price)
}
}
return false
} else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
Log("错误:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
return true
} else {
return true
}
}
これらの機能が戦略要件に従って設計されたので、戦略の主な機能の設計を開始できます。
FMZの戦略はmain関数の実行が開始されます。存在するmain関数の始めに、いくつかの戦略の初期化を行う必要があります。
var exA = exchanges[0]
var exB = exchanges[1]
これにより、後でコードを書くのが非常に快適になります。
// 精度,汇率设置
if (rateA != 1) {
// 设置汇率A
exA.SetRate(rateA)
Log("交易所A设置汇率:", rateA, "#FF0000")
}
if (rateB != 1) {
// 设置汇率B
exB.SetRate(rateB)
Log("交易所B设置汇率:", rateB, "#FF0000")
}
exA.SetPrecision(pricePrecisionA, amountPrecisionA)
exB.SetPrecision(pricePrecisionB, amountPrecisionB)
為替レートパラメータrateA、rateBいくつかは1に設定されています(デフォルトは1です)。rateA != 1またはrateB != 1トリガーされないので、為替レート変換は設定されません。

ポリシーを開始するときに、すべてのログを削除し、記録されたデータをクリアする必要がある場合があります。戦略インターフェースパラメータを設計することができますisReset次に、戦略の初期化セクションでリセット コードを設計します。例:
if (isReset) { // 当isReset为真时重置数据
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
nowAccsこの変数は、先ほど設計した関数を使用して、現在のアカウント データを記録するために使用されます。updateAccs現在の取引所のアカウントデータを取得します。initAccs口座の初期状態(取引所Aと取引所Bのコインの枚数や額面枚数などのデータ)を記録するために使用します。のためにinitAccs初回使用_G()機能回復(_G 関数はデータを永続的に記録し、記録したデータを再度返すことができます。詳細については、API ドキュメントを参照してください。リンク)、クエリが失敗した場合は、現在のアカウント情報を使用して値を割り当て、_G機能レコード。たとえば、次のコード:
var nowAccs = _C(updateAccs, exchanges)
var initAccs = _G("initAccs")
if (!initAccs) {
initAccs = nowAccs
_G("initAccs", initAccs)
}
メイン ループ内のコードは、戦略ロジックの各実行ラウンドのプロセスです。連続した往復実行が戦略のメイン ループを構成します。メインループ内のプログラムの各実行の流れを見てみましょう。
var ts = new Date().getTime()
var depthARoutine = exA.Go("GetDepth")
var depthBRoutine = exB.Go("GetDepth")
var depthA = depthARoutine.wait()
var depthB = depthBRoutine.wait()
if (!depthA || !depthB || depthA.Asks.length == 0 || depthA.Bids.length == 0 || depthB.Asks.length == 0 || depthB.Bids.length == 0) {
Sleep(500)
continue
}
ここでは、FMZ プラットフォームの同時実行機能を確認できます。exchange.Go、コールを作成しましたGetDepth()インターフェースの並行オブジェクトdepthARoutine、depthBRoutine。これら2つの同時オブジェクトが作成されると、GetDepth()インターフェースもすぐに発生し、深度データを取得するための 2 つの要求が取引所に送信されました。
それから電話するdepthARoutine、depthBRoutine物体wait()深度データを取得する方法。
深度データを取得した後、深度データをチェックしてその有効性を判断する必要があります。データ異常時のトリガー実行continueステートメントはメイン ループを再実行します。
价差值パラメータまたは差价比例パラメータ? var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
パラメータの観点からこのような設計をしました。 FMZのパラメータは特定のパラメータに基づいて設定できます見せるまたは隠れる、使用するかどうかを決定するパラメータを作成できます价格差、まだ差价比例。

戦略インターフェースパラメータにパラメータが追加されましたdiffAsPercentage。このパラメータに基づいて表示または非表示になる他の 2 つのパラメータは、次のように設定されます。
hedgeDiffPrice@!diffAsPercentage、いつdiffAsPercentageFalse の場合、このパラメータが表示されます。
hedgeDiffPercentage@diffAsPercentage、いつdiffAsPercentageこのパラメータを表示するには True にします。
このデザインの後、私たちはdiffAsPercentageパラメータは、ヘッジトリガー条件として価格差比率に基づいています。チェックを外すdiffAsPercentageこのパラメータは、価格差をヘッジのトリガー条件として使用します。
if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPrice && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) { // A -> B 盘口条件满足
var price = (depthA.Bids[0].Price + depthB.Asks[0].Price) / 2
var amount = Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount)
if (nowAccs[0].Stocks > minHedgeAmount && nowAccs[1].Balance / price > minHedgeAmount) {
amount = Math.min(amount, nowAccs[0].Stocks, nowAccs[1].Balance / price, maxHedgeAmount)
Log("触发A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks) // 提示信息
hedge(exB, exA, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
} else if (depthB.Bids[0].Price - depthA.Asks[0].Price > targetDiffPrice && Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount) >= minHedgeAmount) { // B -> A 盘口条件满足
var price = (depthB.Bids[0].Price + depthA.Asks[0].Price) / 2
var amount = Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount)
if (nowAccs[1].Stocks > minHedgeAmount && nowAccs[0].Balance / price > minHedgeAmount) {
amount = Math.min(amount, nowAccs[1].Stocks, nowAccs[0].Balance / price, maxHedgeAmount)
Log("触发B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks) // 提示信息
hedge(exA, exB, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
}
ヘッジのトリガー条件はいくつかあります。
1. まず、ヘッジ価格差を満たします。市場の価格差が設定された価格差パラメータを満たしている場合にのみ、ヘッジを実行できます。
2. 市場でのヘッジ量は、パラメータで設定された最小ヘッジ量を満たす必要があります。取引所によって最小注文量が異なる場合があるため、2 つのうち小さい方を採用する必要があります。
3. 取引所には売却操作に必要な資産が十分にあり、また、取引所には購入操作に必要な資産が十分にあります。
これらの条件が満たされると、ヘッジ機能が実行され、ヘッジ注文が発行されます。メイン関数の前に変数を事前に宣言しましたisTradeヘッジが発生したかどうかを示すために使用されます。ヘッジがトリガーされた場合、この変数は次のように設定されます。true。グローバル変数をリセットするlastKeepBalanceTSlastKeepBalanceTS を 0 に設定し (lastKeepBalanceTS は最新のバランス調整操作のタイムスタンプをマークするために使用されます。0 に設定すると、バランス調整操作が直ちにトリガーされます)、保留中の注文をすべてキャンセルします。
if (ts - lastKeepBalanceTS > keepBalanceCyc * 1000) {
nowAccs = _C(updateAccs, exchanges)
var isBalance = keepBalance(initAccs, nowAccs, [depthA, depthB])
cancelAll()
if (isBalance) {
lastKeepBalanceTS = ts
if (isTrade) {
var nowBalance = _.reduce(nowAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
var initBalance = _.reduce(initAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
LogProfit(nowBalance - initBalance, nowBalance, initBalance, nowAccs)
isTrade = false
}
}
}
バランス関数は定期的に実行されていることがわかりますが、ヘッジ操作がトリガーされると、lastKeepBalanceTS0 にリセットすると、バランス操作がすぐにトリガーされます。バランスが取れた後、利益が計算されます。
LogStatus(_D(), "A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, " B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, " targetDiffPrice:", targetDiffPrice, "\n",
"当前A,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n",
"当前B,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n",
"初始A,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n",
"初始B,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
ステータス バーのデザインは特に複雑ではありません。現在の時刻、取引所 A と取引所 B の価格差、取引所 B と取引所 A の価格差が表示されます。現在のヘッジターゲットスプレッドを表示します。 A取引所口座とB取引所口座の資産データを表示します。
パラメータに関しては、戦略の開始時にコンバージョン率の値パラメータを設計しました。main関数の初期動作のために為替レート変換も設計しました。注目すべきはSetRate最初に為替レート変換機能を実行する必要があります。
この関数は次の 2 つのレベルに影響します。
BTC_USDT、価格の単位はUSDT口座資産の利用可能な通貨もUSDT。 CNYに変換したい場合はコードで設定しますexchange.SetRate(6.8)ただexchangeこの交換オブジェクトの下のすべての関数によって取得されたデータは、CNY に変換されます。
換算に通貨が使用されるのはなぜですか?SetRate関数の受け渡し現在の通貨から目標通貨への為替レート。