2021-08-05 16:35:33 +08:00
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Name
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
2023-04-18 23:12:35 +08:00
|
|
|
|
不同计价币的现货对冲策略教学
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Author
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
2025-04-30 11:10:28 +08:00
|
|
|
|
发明者量化-小小梦
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Strategy Description
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
|
|
|
|
|
## 不同计价币的现货对冲策略(教学)
|
|
|
|
|
|
|
|
|
|
|
|
相关文章:https://www.fmz.com/digest-topic/7593
|
|
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Strategy Arguments
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
|Argument|Default|Description|
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|----|----|----|
|
|
|
|
|
|
|keepBalanceCyc|300|平衡周期|
|
|
|
|
|
|
|diffAsPercentage|true|使用差价百分比|
|
|
|
|
|
|
|hedgeDiffPrice|20|对冲差价|
|
|
|
|
|
|
|hedgeDiffPercentage|0.04|对冲差价百分比|
|
|
|
|
|
|
|minHedgeAmount|0.005|对冲最小下单量|
|
|
|
|
|
|
|maxHedgeAmount|0.2|对冲最大下单量|
|
|
|
|
|
|
|rateA|true|A交易所汇率|
|
|
|
|
|
|
|rateB|true|B交易所汇率|
|
|
|
|
|
|
|isReset|false|重置所有信息|
|
|
|
|
|
|
|pricePrecisionA|2|A价格精度|
|
|
|
|
|
|
|amountPrecisionA|3|A下单量精度|
|
|
|
|
|
|
|pricePrecisionB|2|B价格精度|
|
|
|
|
|
|
|amountPrecisionB|3|B下单量精度|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Source (javascript)
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
|
|
|
|
|
``` javascript
|
|
|
|
|
|
// 全局变量
|
|
|
|
|
|
var lastKeepBalanceTS = 0
|
|
|
|
|
|
|
|
|
|
|
|
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 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 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) {
|
|
|
|
|
|
Log("触发平衡操作,平衡量:", Math.abs(diff))
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 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 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)
|
|
|
|
|
|
|
|
|
|
|
|
if (isReset) {
|
|
|
|
|
|
_G(null)
|
|
|
|
|
|
LogReset(1)
|
|
|
|
|
|
LogProfitReset()
|
|
|
|
|
|
LogVacuum()
|
|
|
|
|
|
Log("重置所有数据", "#FF0000")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var nowAccs = _C(updateAccs, exchanges)
|
|
|
|
|
|
var initAccs = _G("initAccs")
|
|
|
|
|
|
if (!initAccs) {
|
|
|
|
|
|
initAccs = nowAccs
|
|
|
|
|
|
_G("initAccs", initAccs)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var isTrade = false
|
|
|
|
|
|
while (true) {
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var targetDiffPrice = hedgeDiffPrice
|
|
|
|
|
|
if (diffAsPercentage) {
|
|
|
|
|
|
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
|
|
Sleep(1000)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Detail
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
|
|
|
|
|
https://www.fmz.com/strategy/298752
|
|
|
|
|
|
|
2023-07-14 18:39:48 +08:00
|
|
|
|
> Last Modified
|
2021-08-05 16:35:33 +08:00
|
|
|
|
|
|
|
|
|
|
2021-07-30 16:54:39
|