Для новичков в разработке стратегии, стратегия хеджирования является очень хорошей для практики.
В первую очередь, мы должны убедиться, что стратегия, которая должна быть разработана, является криптовалютной спотовой стратегией хеджирования. Мы разрабатываем самую простую стратегию хеджирования. Мы продаем только на платформе с более высокой ценой между двумя спотовыми платформами и покупаем на платформе с более низкой ценой, чтобы заработать спред цены. Когда платформа с более высокой ценой полна котировочных валютных символов (потому что цена высока, все валютные символы продаются), или когда платформа с более низкой ценой полна валютных символов (потому что цена низкая, валютные символы покупаются всеми активами), ее нельзя хеджировать. В это время вы можете только ждать, пока цена перевернется для хеджирования.
Для цены заказа и суммы во время хеджирования в каждой платформе существуют пределы точности, а также есть ограничение на минимальную сумму заказа. В дополнение к минимальному пределу стратегия также должна учитывать максимальную сумму заказа для хеджирования. Если сумма заказа слишком велика, то на рынке не будет достаточного объема заказов для этого. Также необходимо учитывать, как конвертировать обменный курс, если у двух платформ разные котировочные валюты. Сборы за обработку во время хеджирования и проскальзывание покупателя заказа - это все торговые расходы. Хеджирование не всегда происходит до тех пор, пока существует разница в цене. Поэтому спред цены хеджирования также имеет триггерное значение. Если он ниже определенного спреда цены, хеджирование принесет убыток.
Исходя из этого, стратегия должна быть разработана с несколькими параметрами:
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()
}
Наконец, давайте завершим конструкцию функции баланса, которая немного сложнее.
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
// calculate currency spread
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("cannot balance")
} else {
// balanced ordering
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("invalid price", price)
}
}
return false
} else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
Log("error:", "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]
Тогда будет удобнее написать код позже.
Обменный курс и точность
// settings of precision and exchange rate
if (rateA != 1) {
// set exchange rate A
exA.SetRate(rateA)
Log("Platform A sets exchange rate:", rateA, "#FF0000")
}
if (rateB != 1) {
// set exchange rate B
exB.SetRate(rateB)
Log("Platform B sets exchange rate:", rateB, "#FF0000")
}
exA.SetPrecision(pricePrecisionA, amountPrecisionA)
exB.SetPrecision(pricePrecisionB, amountPrecisionB)
Если один из параметров обменного курса, а именноrateA
иrateB
, устанавливается на 1 (по умолчанию это 1), то естьrateA != 1
илиrateB != 1
означает не задействованный, и обменный курс не может быть конвертирован.
Перезагрузить все даты
Иногда необходимо удалить все журналы и очистить записи данных при запуске стратегии.isReset
, а затем спроектировать код сброса в части инициализации стратегии, например:
if (isReset) { // when "isReset" is true, reset the data
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("Reset all data", "#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
}
Здесь вы можете видеть, что одновременная функцияexchange.Go
Платформа FMZ используется для создания одновременных объектовdepthARoutine
иdepthBRoutine
что называютGetDepth()
Когда эти два одновременных объектов создаются,GetDepth()
Интерфейс вызван немедленно, и оба запроса на глубину данных отправляются на платформу.
Тогда позвонитеwait()
метод объектаdepthARoutine
и объектdepthBRoutine
чтобы получить данные о глубине.
После получения данных глубины необходимо проверить данные глубины для оценки их достоверности.continue
Указание запускается для повторного выполнения основной петли.
Использованиеprice spread
илиspread ratio
?
var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
С точки зрения параметров, мы сделали такую конструкцию.показыватьилипрятатьсяна основе параметра, так что мы можем сделать параметр, чтобы решить, следует ли использоватьprice spread
, илиspread ratio
.
ПараметрdiffAsPercentage
Другие два параметра, которые будут отображаться или скрываться в зависимости от параметра, установлены как:hedgeDiffPrice@!diffAsPercentage
; когдаdiffAsPercentage
ложь, это будет показано.hedgeDiffPercentage@diffAsPercentage
; когдаdiffAsPercentage
Это правда, она будет показана.
После проектирования мы проверили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 market condition satisfied
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("triggerA->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks) // prompt message
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 market condition satisfied
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("triggerB->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks) // prompt message
hedge(exA, exB, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
}
Для хеджирования существует несколько триггерных условий: 1.Во-первых, соблюдать спред хеджирования; хеджирование возможно только тогда, когда рыночный спред соответствует установленному параметру спреда.
2.Сумма хеджирования рынка должна соответствовать минимальной сумме хеджирования, установленной в параметрах.Поскольку минимальная сумма ордера разных платформ различна, следует выбрать самую маленькую из двух.
3.Активов на платформе с операцией продажи достаточно для продажи, а активов на платформе с операцией покупки достаточно для покупки. Когда эти условия выполнены, выполняется функция хеджирования для размещения заказов по хеджированию. Перед основной функцией мы объявляем переменнуюisTrade
Здесь, если хедж запускается, переменная устанавливается наtrue
. И перезагрузить глобальную переменную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",
"currentA,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n",
"currentB,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n",
"initialA,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n",
"initialB,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
Полоса состояния не предназначена для особой сложности. Она отображает текущее время, спред цен от платформы А к платформе В, а также спред цен от B к A; она также отображает текущий целевой спред хеджирования, данные актива счета платформы А и данные актива счета платформы В.
С точки зрения параметров, мы разработали параметр конвертации валютного курса стоимости, и мы также разработали конвертацию валютного курса в первоначальной операцииmain
При этом необходимо отметить, чтоSetRate
Функция конвертации обменного курса должна быть выполнена сначала.
Для функции будет влиять два аспекта:
Например, текущая торговая параBTC_USDT
, ценная единицаUSDT
, а наличная валюта котировки в активах счета такжеUSDT
Если я хочу конвертировать стоимость активов в юанях, установитьexchange.SetRate(6.8)
в коде для преобразования данных, полученных всеми функциями в соответствии сexchange
Объект, а затем конвертировать в CNY.
Чтобы конвертировать в какую валюту котировки, импортироватьобменный курс от текущей валюты котировки к целевой валюте котировкивSetRate
function.
Полная стратегия:Стратегия спотового хеджирования различных котируемых валют (обучение)