Pour les débutants dans la conception de stratégies, les stratégies de couverture sont de très bonnes stratégies d'entraîneur. Cet article met en œuvre une stratégie de couverture instantanée de crypto-monnaie simple mais pratique, dans l'espoir de permettre aux débutants d'apprendre une partie de l'expérience de conception.
Tout d'abord, il est clair que la stratégie à venir est une stratégie de couverture contre les pièces de monnaie numérique, nous avons conçu la couverture la plus simple, en vendant uniquement les pièces les plus chères entre les deux échanges de monnaie, en achetant les pièces les moins chères et en profitant du décalage.
Le prix, la quantité et la précision des commandes sont limitées et il y a une limite minimale. En plus de la limite minimale, les stratégies non limitées de la couverture prennent également en compte la quantité maximale d'un seul ordre de couverture, car il n'y aura pas assez de volume d'ordre si le montant est trop important.
Sur la base de ces considérations, la stratégie doit concevoir plusieurs paramètres:
hedgeDiffPrice
La différence de prix entre les deux prix est de 0,01%.minHedgeAmount
Le taux de change est le plus bas possible.maxHedgeAmount
Le taux d'intérêt de la banque est le plus bas.pricePrecisionA
La précision des prix des commandes d'un échange A (en minuscules chiffres).amountPrecisionA
La précision de l'échantillon est la plus basse sur A.pricePrecisionB
La précision des prix des commandes sur l'échange B (en minuscules chiffres)amountPrecisionB
La précision de l'échantillon B est la précision de l'échantillon B.rateA
, la conversion du taux de change du premier objet d'échange ajouté n'est pas convertie par défaut 1.rateB
, la conversion du taux de change du deuxième objet d'échange ajouté, le 1 par défaut n'est pas converti.Une stratégie de contrebande nécessite de maintenir le nombre de pièces dans les deux comptes inchangé (c'est-à-dire de ne pas tenir de position dans n'importe quelle direction, de rester neutre) et donc une logique d'équilibre est nécessaire.
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
}
Si une commande n'est pas passée après l'ordre, nous devons annuler l'ordre en temps opportun, nous ne pouvons pas la laisser pendue. Cette opération doit être traitée dans le module d'équilibrage ou dans la logique de couverture, il est donc nécessaire de concevoir une fonction d'annulation complète de l'ordre.
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)
}
}
})
}
Pour équilibrer le nombre de pièces, nous avons besoin de trouver un prix qui s'accumule à un certain nombre de pièces dans une certaine profondeur de données, donc nous avons besoin d'une fonction comme celle-ci pour traiter.
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
}
Ensuite, nous avons besoin de concevoir et d'écrire des opérations de sous-ordonnance pour des opérations de sous-ordonnance spécifiques, qui doivent être conçues comme des sous-ordonnances simultanées:
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()
}
Enfin, nous avons terminé la conception de la fonction d'équilibre, qui est un peu plus complexe.
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
}
}
Les fonctions ont été conçues en fonction des besoins de la stratégie, et vous pouvez commencer à concevoir les principales fonctions de la stratégie ci-dessous.
La stratégie sur FMZ est de:main
La fonction commence à s'exécuter.main
La partie du début de la fonction où nous allons initialement travailler sur certaines stratégies.
Nom de l'objet Comme beaucoup d'opérations de stratégie sont utilisées pour les objets d'échange, tels que l'acquisition d'un marché, la commande, etc. Il est donc gênant d'utiliser un nom plus long à chaque fois, une petite astuce consiste à utiliser un nom plus simple, par exemple:
var exA = exchanges[0]
var exB = exchanges[1]
Il est donc plus facile d'écrire du code par la suite.
Des devises, des conceptions de précision
// 精度,汇率设置
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)
Si le paramètre de changerateA
、rateB
Il y a une valeur de 1 (par défaut 1) qui est:rateA != 1
ourateB != 1
Le taux de conversion n'est pas activé, donc il n'est pas configuré.
Réinitialiser toutes les données
Il est parfois nécessaire de supprimer tous les journaux et les données de l'enregistrement vide lors du démarrage de la politique.isReset
, puis réinitialisez la partie du code de conception dans la stratégie, par exemple:
if (isReset) { // 当isReset为真时重置数据
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
Récupérer les données du compte initial, mettre à jour les données du compte actuel
Pour juger de l'équilibre, la stratégie nécessite de documenter en permanence la situation des actifs du compte initial par rapport à la situation actuelle.nowAccs
C'est la variable qui est utilisée pour enregistrer les données des comptes en cours, en utilisant les fonctions que nous venons de concevoir.updateAccs
Pour obtenir les données de compte de l'échange en cours.initAccs
Pour enregistrer l'état initial du compte (données telles que le nombre de pièces d'échange A et B, le nombre de pièces de monnaie cotées)initAccs
D'abord utilisé_G()
Récupération de la fonction ((_G) La fonction _G enregistrera les données de manière permanente et peut récupérer les données enregistrées.Les liensSi vous n'avez pas accès à la requête, attribuez-la et utilisez les informations actuelles de votre compte._G
Les fonctions sont enregistrées.
Par exemple, le code suivant:
var nowAccs = _C(updateAccs, exchanges)
var initAccs = _G("initAccs")
if (!initAccs) {
initAccs = nowAccs
_G("initAccs", initAccs)
}
Le code dans la boucle principale est le processus qui est exécuté à chaque tour de la logique stratégique, et les exécutions répétées constituaient la boucle principale.
Les données de marché peuvent être utilisées pour évaluer l'efficacité des données de marché.
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
}
Vous pouvez voir ici les fonctions de synchronisation utilisant la plateforme FMZ.exchange.Go
Nous avons créé un appel.GetDepth()
Objets simultanés d'interfacedepthARoutine
、depthBRoutine
Les deux objets simultanés sont créés en appelantGetDepth()
L'interface est également apparue immédiatement, lorsque les deux demandes d'accès à des données profondes ont été envoyées à l'échange.
Puis appelle.depthARoutine
、depthBRoutine
l'objetwait()
Les méthodes pour obtenir des données en profondeur.
Une fois que les données en profondeur ont été obtenues, il est nécessaire de les examiner pour déterminer leur efficacité.continue
Les phrases sont réécrites dans le cycle principal.
Utilisation价差值
Les paramètres sont差价比例
Les paramètres?
var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
Les paramètres que nous avons conçus sont les suivants: FMZ peut être basé sur un paramètre.AffichageOu alorsIls se cachent.Donc nous pouvons faire un paramètre pour décider si on utilise价格差
Je ne sais pas.差价比例
。
Un paramètre a été ajouté aux paramètres de l'interface stratégiquediffAsPercentage
Les deux autres paramètres affichés ou cachés basés sur ce paramètre sont:hedgeDiffPrice@!diffAsPercentage
Je ne sais pasdiffAsPercentage
Faux pour afficher ce paramètre.hedgeDiffPercentage@diffAsPercentage
Je ne sais pasdiffAsPercentage
Le paramètre doit être affiché correctement.
C'est ce que nous avons fait.diffAsPercentage
Paramètres, c'est-à-dire les conditions de déclenchement de la couverture en fonction de la différence de prix.diffAsPercentage
Le paramètre est la différence de prix comme condition de déclenchement de la couverture.
Déterminez les conditions de déclenchement de la couverture
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
}
}
Les conditions pour déclencher une contrepartie sont les suivantes:
1, la différence de couverture est satisfaite en premier lieu et ne peut être couverte que si la différence de couverture satisfait aux paramètres de différence définis.
2° La valeur de l'endettement doit satisfaire à la valeur minimale de l'endettement définie dans le paramètre, car la valeur minimale de l'endettement peut varier selon les échanges.
3° les actifs de l'échange sur lequel l'opération a été vendue sont suffisamment vendus et les actifs de l'échange sur lequel l'opération a été achetée sont suffisamment achetés;
Lorsque ces conditions sont remplies, l'exécution de la fonction de couverture est couverte. Avant la fonction principale, nous déclarons une variable.isTrade
Pour marquer si une couverture est en cours, si la couverture est déclenchée, la variable esttrue
Et réinitialisez les variables globales.lastKeepBalanceTS
Pour 0 ((lastKeepBalanceTS est utilisé pour marquer la barre de temps de la dernière opération de mise en équilibre, la mise à 0 déclenche immédiatement l'opération de mise en équilibre), puis supprimez toutes les listes suspendues.
Opération de mise en équilibre
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
}
}
}
Vous pouvez voir que la fonction d'équilibrage est exécutée régulièrement, mais si une opération de couverture est déclenchée après, la fonction d'équilibrage est déclenchée après.lastKeepBalanceTS
Une opération de mise en équilibre est immédiatement déclenchée si elle est réinitialisée à 0.
Informations sur le panneau de statut
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)
La barre d'état n'est pas particulièrement complexe en termes de conception, elle affiche l'heure actuelle, la différence d'un échange A à un échange B et celle d'un échange B à un échange A. Elle affiche la différence d'objectif de couverture actuelle. Elle affiche les données d'actifs des comptes d'un échange A et celles des comptes d'actifs d'un échange B.
Dans les paramètres, nous avons conçu les paramètres de conversion des taux de change, et nous avons commencé la stratégie.main
Nous avons également conçu la conversion des taux de change pour la partie de l'opération initiale de la fonction.SetRate
La fonction de conversion des taux de change doit être exécutée en premier.
La fonction fonctionne à deux niveaux:
BTC_USDT
Les prix sont les mêmesUSDT
La monnaie utilisée dans les actifs de compte est également la devise.USDT
Si je veux convertir une valeur en CNY, je la mets dans le code.exchange.SetRate(6.8)
Je vous en prie.exchange
Toutes les données obtenues par les fonctions sous cet objet d'échange sont converties en CNY.
En échange de quoi les monnaies sont-elles évaluées?SetRate
Transfert de fonctionTaux de change de la devise actuelle à la devise cible。La stratégie complète:Les stratégies de couverture des devises en cours d'échange (enseignement)
Les écureuils sont des hôtes de l'Ukraine.C'était génial.