
Для новичков в разработке стратегий стратегия хеджирования является очень хорошей учебной стратегией. В этой статье реализуется простая, но реалистичная стратегия хеджирования спотовых сделок с цифровой валютой, которая поможет новичкам получить некоторый опыт проектирования.
Прежде всего, очевидно, что стратегия, которая будет разработана, — это стратегия спотового хеджирования цифровой валюты. Мы разрабатываем простейшую стратегию хеджирования, которая заключается в продаже на бирже с более высокой ценой между двумя спотовыми биржами и покупке на бирже с более низкой ценой. чтобы получить прибыль. Возьмите разницу. Когда все биржи с более высокими ценами номинированы в монетах (потому что все монеты с более высокими ценами продаются), а все биржи с более низкими ценами номинированы в монетах (все монеты с более низкими ценами покупаются), хеджирование невозможно. В это время вам остается только ждать разворота цены и хеджироваться.
При хеджировании биржа имеет ограничения по точности цены и объема заказа, а также существует ограничение по минимальному объему заказа. Помимо минимального лимита, стратегия должна также учитывать максимальный объем ордера для хеджирования за один раз. Если объем ордера слишком большой, на рынке не будет достаточно ордеров. Вам также необходимо подумать о том, как конвертировать, используя обменный курс, если две валюты выражены в разных валютах. При хеджировании сборы за обработку и проскальзывание ордеров являются транзакционными издержками. Хеджирование невозможно, пока есть разница в цене. Поэтому существует также значение триггера для хеджирования разницы в цене. Когда разница в цене ниже, чем определенного уровня хеджирование приведет к убыткам.
Исходя из этих соображений, стратегия должна включать несколько параметров:
hedgeDiffPriceкогда разница в цене превышает это значение, запускается операция хеджирования.minHedgeAmount, минимальный объем заказа (в монетах), который можно хеджировать.maxHedgeAmount, максимальный объем заказа (количество монет) для одного хеджа.pricePrecisionA, точность цены заказа (количество знаков после запятой) биржи А.amountPrecisionA, точность количества заказа (количество знаков после запятой) обмена А.pricePrecisionB, точность цены заказа (количество знаков после запятой) биржи B.amountPrecisionB, точность количества заказа (десятичные знаки) обмена B.rateA, конвертация обменного курса первого добавленного объекта обмена, значение по умолчанию — 1, без конвертации.rateB, конвертация обменного курса второго добавленного объекта обмена, значение по умолчанию равно 1 и конвертация не выполняется.Стратегия хеджирования должна поддерживать неизменное количество монет на двух счетах (то есть не занимать никаких направленных позиций и сохранять нейтралитет), поэтому в стратегии должна быть логика баланса, чтобы всегда проверять баланс. При проверке баланса неизбежно получение данных об активах с двух бирж. Нам нужно написать функцию для его использования.
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. Когда эти два параллельных объекта созданы, вызовитеGetDepth()Интерфейс также появился немедленно, и на биржу было отправлено два запроса на получение данных о глубине.
Тогда позвоните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
}
Мы сделали такую конструкцию по параметрам. Параметры ФМЗ могут быть основаны на определенном параметрепоказыватьилискрывать, поэтому мы можем создать параметр, который решит, использовать ли价格差,все еще差价比例。

В параметры интерфейса стратегии добавлен параметрdiffAsPercentage. Два других параметра, которые отображаются или скрываются на основе этого параметра, устанавливаются следующим образом:
hedgeDiffPrice@!diffAsPercentage,когдаdiffAsPercentageЗначение False отображает этот параметр.
hedgeDiffPercentage@diffAsPercentage,когдаdiffAsPercentageTrue для отображения этого параметра.
После этого дизайна мы проверили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. Объем хеджирования на рынке должен соответствовать минимальному объему хеджирования, установленному в параметрах. Поскольку на разных биржах могут быть разные минимальные объемы ордеров, следует брать наименьший из двух.
3. На бирже достаточно активов для операций по продаже, чтобы продать, и на бирже достаточно активов для операций по покупке, чтобы купить.
При выполнении этих условий выполняется функция хеджирования для размещения ордера на хеджирование. Перед основной функцией мы заранее объявили переменнуюisTradeИспользуется для обозначения того, происходит ли хеджирование. Если хеджирование срабатывает, эта переменная устанавливается вtrue. И сбросить глобальные переменныеlastKeepBalanceTSУстановите lastKeepBalanceTS на 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
}
}
}
Вы видите, что функция баланса выполняется регулярно, но если запускается операция хеджирования,lastKeepBalanceTSПри сбросе на 0 операция балансировки будет запущена немедленно. После успешного баланса будет рассчитана прибыль.
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Сначала необходимо выполнить функцию конвертации обменного курса.
Потому что эта функция влияет на два уровня:
BTC_USDT, единицы ценыUSDTДоступная валюта в активах счета составляетUSDT. Если я хочу конвертировать его в CNY, укажите это в кодеexchange.SetRate(6.8)ТолькоexchangeДанные, полученные всеми функциями в рамках этого объекта обмена, конвертируются в CNY.
Почему для конвертации используется именно эта валюта?SetRateПередача функцииОбменный курс из текущей валюты в целевую валюту。Полная стратегия:Стратегии спотового хеджирования для разных валют (учебник)