
Ultimamente, tem havido muitas discussões sobre estratégias de Martingale no grupo oficial da FMZ, mas não há muitas estratégias de Martingale para contratos de moeda digital na plataforma. Por isso, aproveitei a oportunidade para criar uma estratégia simples de Martingale para futuros de criptomoedas. Por que é chamada de estratégia Martingale? Porque os riscos potenciais da estratégia Martingale não são realmente pequenos, então ela não é projetada inteiramente de acordo com a estratégia Martingale. No entanto, esse tipo de estratégia ainda apresenta riscos consideráveis, e as configurações dos parâmetros da estratégia Martingale estão intimamente relacionadas aos riscos, portanto, os riscos não devem ser ignorados.
Este artigo explica e estuda principalmente o design de estratégias do tipo Martin. A ideia da estratégia em si é muito clara. Como usuários do FMZ, consideramos mais o design da estratégia.
Ao projetar estratégias de futuros de moeda digital, os dados de patrimônio líquido total são frequentemente usados. Porque é necessário calcular os retornos, especialmente quando é necessário calcular retornos flutuantes. Como as posições abertas ocupam margem, as ordens pendentes também ocupam margem. Neste momento, chame a interface API da plataforma FMZexchange.GetAccount()O que é obtido são os ativos disponíveis e os ativos congelados por ordens pendentes. De fato, a maioria das bolsas de futuros de moeda digital fornece dados sobre o patrimônio total, mas a FMZ não encapsula esse atributo de maneira uniforme.
Portanto, projetamos funções para obter esses dados de acordo com diferentes trocas:
// OKEX V5 获取总权益
function getTotalEquity_OKEX_V5() {
var totalEquity = null
var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "ccy=USDT")
if (ret) {
try {
totalEquity = parseFloat(ret.data[0].details[0].eq)
} catch(e) {
Log("获取账户总权益失败!")
return null
}
}
return totalEquity
}
// 币安期货
function getTotalEquity_Binance() {
var totalEquity = null
var ret = exchange.GetAccount()
if (ret) {
try {
totalEquity = parseFloat(ret.Info.totalWalletBalance)
} catch(e) {
Log("获取账户总权益失败!")
return null
}
}
return totalEquity
}
No códigototalEquityEsse é o patrimônio total que precisamos. Em seguida, escrevemos uma função como entrada de chamada e chamamos a função correspondente de acordo com o nome da troca.
function getTotalEquity() {
var exName = exchange.GetName()
if (exName == "Futures_OKCoin") {
return getTotalEquity_OKEX_V5()
} else if (exName == "Futures_Binance") {
return getTotalEquity_Binance()
} else {
throw "不支持该交易所"
}
}
Antes de projetar a função principal e a lógica principal. Ainda precisamos fazer alguma preparação e projetar algumas funções auxiliares.
function cancelAll() {
while (1) {
var orders = _C(exchange.GetOrders)
if (orders.length == 0) {
break
}
for (var i = 0 ; i < orders.length ; i++) {
exchange.CancelOrder(orders[i].Id, orders[i])
Sleep(500)
}
Sleep(500)
}
}
Acredito que aqueles que frequentemente olham para os códigos de exemplo de estratégia no FMZ Strategy Square estão muito familiarizados com essa função. Muitas estratégias usaram designs semelhantes. Sua função é obter a lista atual de pedidos pendentes e então cancelá-los um por um.
function trade(distance, price, amount) {
var tradeFunc = null
if (distance == "buy") {
tradeFunc = exchange.Buy
} else if (distance == "sell") {
tradeFunc = exchange.Sell
} else if (distance == "closebuy") {
tradeFunc = exchange.Sell
} else {
tradeFunc = exchange.Buy
}
exchange.SetDirection(distance)
return tradeFunc(price, amount)
}
function openLong(price, amount) {
return trade("buy", price, amount)
}
function openShort(price, amount) {
return trade("sell", price, amount)
}
function coverLong(price, amount) {
return trade("closebuy", price, amount)
}
function coverShort(price, amount) {
return trade("closesell", price, amount)
}
Existem quatro direções na negociação de futuros: abrir uma posição longa (openLong), abrir uma posição curta (openShort), fechar uma posição longa (coverLong) e fechar uma posição curta (coverShort). Então, projetamos quatro funções de ordem para corresponder a essas operações. Se você estiver pensando apenas em fazer um pedido, há vários fatores necessários: direção, preço do pedido e quantidade do pedido.
Então também criamos um programa chamado:tradeA função para manipular方向(distance)、下单价格(price)、下单量(amount)Todas as operações são claras.
As chamadas de função de abertura de uma posição longa (openLong), abertura de uma posição curta (openShort), fechamento de uma posição longa (coverLong) e fechamento de uma posição curta (coverShort) são executadas portradeA função executa a função real, que é colocar uma ordem na bolsa de futuros de acordo com a direção, preço e quantidade estabelecidos.
A estratégia é muito simples. Use o preço atual como base e coloque ordens de venda (curta) e ordens de compra (longa) a uma certa distância acima e abaixo do preço atual. Uma vez que um lado é executado, todas as ordens restantes serão canceladas e, em seguida, uma nova ordem de fechamento será colocada a uma certa distância com base no preço da posição, e uma ordem de aumento será colocada no preço atual atualizado, mas a ordem de aumento será não dobrar a quantidade do pedido.
var buyOrderId = null
var sellOrderId = null
Então a opção de usar o disco de simulação OKEX_V5 é projetada nos parâmetros da interface de estratégia, então algum processamento precisa ser feito no código:
var exName = exchange.GetName()
// 切换OKEX V5模拟盘
if (isSimulate && exName == "Futures_OKCoin") {
exchange.IO("simulate", true)
}
Os parâmetros da interface também incluem uma opção para redefinir todas as informações, então o código também deve ter o processamento correspondente:
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
Executamos apenas contratos perpétuos, por isso está codificado aqui e definido apenas para contratos perpétuos.
exchange.SetContractType("swap")
Então também precisamos considerar a precisão dos preços dos pedidos e das quantidades dos pedidos. Se a precisão não for definida corretamente, a precisão será perdida durante o processo de cálculo da estratégia. Se os dados tiverem muitas casas decimais, é fácil que o pedido seja rejeitado pela interface de troca.
exchange.SetPrecision(pricePrecision, amountPrecision)
Log("设置精度", pricePrecision, amountPrecision)
Função simples de recuperação de dados
if (totalEq == -1 && !IsVirtual()) {
var recoverTotalEq = _G("totalEq")
if (!recoverTotalEq) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
totalEq = currTotalEq
_G("totalEq", currTotalEq)
} else {
throw "获取初始权益失败"
}
} else {
totalEq = recoverTotalEq
}
}
Se você quiser especificar o patrimônio total inicial da conta quando a estratégia for executada, você pode definir o parâmetrototalEqSe este parâmetro for definido como -1, a estratégia lerá os dados de patrimônio líquido total armazenados. Se não houver dados de patrimônio líquido total armazenados, o patrimônio líquido total lido atual será usado como o patrimônio líquido total inicial do progresso da estratégia em execução. Se o total o patrimônio líquido aumenta, significa que se você ganha dinheiro, mas seu patrimônio líquido total é menor, significa que você perdeu dinheiro. Se os dados de patrimônio líquido total forem lidos, continue a execução usando esses dados.
while (1) { // 策略主要逻辑设计为一个死循环
var ticker = _C(exchange.GetTicker) // 首先读取当前行情信息,主要用到最新成交价
var pos = _C(exchange.GetPosition) // 读取当前持仓数据
if (pos.length > 1) { // 判断持仓数据,由于这个策略的逻辑,是不太可能同时出现多空持仓的,所以发现同时出现多空持仓就抛出错误
Log(pos)
throw "同时有多空持仓" // 抛出错误,让策略停止
}
// 根据状态而定
if (pos.length == 0) { // 根据持仓状态做出不同操作,pos.length == 0是当没有持仓时
// 未持仓了,统计一次收益
if (!IsVirtual()) {
var currTotalEq = getTotalEquity()
if (currTotalEq) {
LogProfit(currTotalEq - totalEq, "当前总权益:", currTotalEq)
}
}
buyOrderId = openLong(ticker.Last - targetProfit, amount) // 挂开多仓的买单
sellOrderId = openShort(ticker.Last + targetProfit, amount) // 挂开空仓的卖单
} else if (pos[0].Type == PD_LONG) { // 有多头持仓,挂单位置、数量有所不同
var n = 1
var price = ticker.Last
buyOrderId = openLong(price - targetProfit * n, amount)
sellOrderId = coverLong(pos[0].Price + targetProfit, pos[0].Amount)
} else if (pos[0].Type == PD_SHORT) { // 有空头持仓,挂单位置、数量有所不同
var n = 1
var price = ticker.Last
buyOrderId = coverShort(pos[0].Price - targetProfit, pos[0].Amount)
sellOrderId = openShort(price + targetProfit * n, amount)
}
if (!sellOrderId || !buyOrderId) { // 如果有一边挂单失败就取消所有挂单,重来
cancelAll()
buyOrderId = null
sellOrderId = null
continue
}
while (1) { // 挂单完成,开始监控订单
var isFindBuyId = false
var isFindSellId = false
var orders = _C(exchange.GetOrders)
for (var i = 0 ; i < orders.length ; i++) {
if (buyOrderId == orders[i].Id) {
isFindBuyId = true
}
if (sellOrderId == orders[i].Id) {
isFindSellId = true
}
}
if (!isFindSellId && !isFindBuyId) { // 检测到买卖单都成交了
cancelAll()
break
} else if (!isFindBuyId) { // 检测到买单成交
Log("买单成交")
cancelAll()
break
} else if (!isFindSellId) { // 检测到卖单成交
Log("卖单成交")
cancelAll()
break
}
LogStatus(_D())
Sleep(3000)
}
Sleep(500)
}
Toda a lógica e design foram explicados.
Deixe a estratégia experimentar a situação do mercado em 19 de maio.


Pode-se observar que a estratégia Martingale ainda apresenta certos riscos.

Endereço estratégico: https://www.fmz.com/strategy/294957
As estratégias são usadas principalmente para aprendizado, então use dinheiro real com cautela ~!