Recentemente, alguns dos usuários da nossa plataforma estão ansiosos para transplantar uma estratégia MyLanguage em uma estratégia JavaScript, que pode adicionar com flexibilidade muitas ideias de otimização. Até mesmo expandir a estratégia para versões multi-espécies. Porque a estratégia MyLanguage é geralmente uma estratégia de tendência, e muitas delas são executadas com base no modelo de preço de fechamento. A interface API da troca de solicitações de estratégia não é muito frequente, o que é adequado para transplantar para versão de estratégia multi-espécies. Neste artigo, vamos tomar uma estratégia MyLanguage simples como exemplo para transplantar para uma versão simples da linguagem JavaScript. O objetivo principal é ensinar e apoiar a pesquisa. Se você quiser fazer um bot real, você pode precisar adicionar alguns detalhes (preço de teste de pedido, precisão de quantidade, controle de quantidade de pedido, exibição de informações de status de ativos, etc.), e também precisa de um teste de bot real.
TR:=MAX(MAX((H-L),ABS(REF(C,1)-H)),ABS(REF(C,1)-L));
ATR:=EMA(TR,LENGTH2);
MIDLINE^^EMA((H + L + C)/3,LENGTH1);
UPBAND^^MIDLINE + N*ATR;
DOWNBAND^^MIDLINE - N*ATR;
BKVOL=0 AND C>=UPBAND AND REF(C,1)<REF(UPBAND,1),BPK;
SKVOL=0 AND C<=DOWNBAND AND REF(C,1)>REF(DOWNBAND,1),SPK;
BKVOL>0 AND C<=MIDLINE,SP(BKVOL);
SKVOL>0 AND C>=MIDLINE,BP(SKVOL);
// stop loss
// stop loss
C>=SKPRICE*(1+SLOSS*0.01),BP;
C<=BKPRICE*(1-SLOSS*0.01),SP;
AUTOFILTER;
A lógica de negociação desta estratégia é simples. Primeiro, calcule o ATR de acordo com os parâmetros, em seguida, calcule a média dos preços mais altos, mais baixos e de fechamento de todos os BARs da linha K e, em seguida, calcule o indicador EMA de acordo com os dados médios. Finalmente, combine o ATR e o coeficiente N em parâmetros para calcular o upBand e o downBand.
As posições de abertura e venda são baseadas no preço de fechamento. Abre posições longas quando ultrapassar a upBand e vende a posição de abertura (quando mantiver posições curtas). Abre posições curtas quando ultrapassar a downBand e vende a posição de abertura. Quando o preço de fechamento atingir a linha média, a posição será fechada, e quando o preço de fechamento atingir o preço de stop loss, a posição também será fechada (de acordo com o SLOSS, o stop loss é 1, ou seja, 0,01, ou seja, 1%). A estratégia é executada num modelo de preço de fechamento.
OK, se entendermos os requisitos estratégicos e ideias do MyLanguage, podemos começar a transplantar.
Não há muitos códigos de protótipo de estratégia, que variam de 1 a 200 linhas.
// parse params parameters, and parse strings as objects
var arrParam = JSON.parse(params)
// this function creates a chart configuration
function createChartConfig(symbol, atrPeriod, emaPeriod, index) { // symbol : trading pair, atrPeriod : ATR parameter period , emaPeriod : EMA parameter period, exchange object index corresponding to index
var chart = {
__isStock: true,
extension: {
layout: 'single',
height: 600,
},
title : { text : symbol},
xAxis: { type: 'datetime'},
series : [
{
type: 'candlestick', // K-line data series
name: symbol,
id: symbol + "-" + index,
data: []
}, {
type: 'line', // EMA
name: symbol + ',EMA:' + emaPeriod,
data: [],
}, {
type: 'line', // upBand
name: symbol + ',upBand' + atrPeriod,
data: []
}, {
type: 'line', // downBand
name: symbol + ',downBand' + atrPeriod,
data: []
}, {
type: 'flags',
onSeries: symbol + "-" + index,
data: [],
}
]
}
return chart
}
// main Logic
function process(e, kIndex, c) { // e is the exchange object, exchanges [0]..., kIndex is the K-line data series in the chart, and c is the chart object
// obtain K-line data
var r = e.GetRecords(e.param.period)
if (!r || r.length < e.param.atrPeriod + 2 || r.length < e.param.emaPeriod + 2) {
// if the K-line data length is insufficient, return
return
}
// calculate ATR indicators
var atr = TA.ATR(r, e.param.atrPeriod)
var arrAvgPrice = []
_.each(r, function(bar) {
arrAvgPrice.push((bar.High + bar.Low + bar.Close) / 3)
})
// calculate EMA indicators
var midLine = TA.EMA(arrAvgPrice, e.param.emaPeriod)
// calculate upBand and downBand
var upBand = []
var downBand = []
_.each(midLine, function(mid, index) {
if (index < e.param.emaPeriod - 1 || index < e.param.atrPeriod - 1) {
upBand.push(NaN)
downBand.push(NaN)
return
}
upBand.push(mid + e.param.trackRatio * atr[index])
downBand.push(mid - e.param.trackRatio * atr[index])
})
// draw the chart
for (var i = 0 ; i < r.length ; i++) {
if (r[i].Time == e.state.lastBarTime) {
// update
c.add(kIndex, [r[i].Time, r[i].Open, r[i].High, r[i].Low, r[i].Close], -1)
c.add(kIndex + 1, [r[i].Time, midLine[i]], -1)
c.add(kIndex + 2, [r[i].Time, upBand[i]], -1)
c.add(kIndex + 3, [r[i].Time, downBand[i]], -1)
} else if (r[i].Time > e.state.lastBarTime) {
// add
e.state.lastBarTime = r[i].Time
c.add(kIndex, [r[i].Time, r[i].Open, r[i].High, r[i].Low, r[i].Close])
c.add(kIndex + 1, [r[i].Time, midLine[i]])
c.add(kIndex + 2, [r[i].Time, upBand[i]])
c.add(kIndex + 3, [r[i].Time, downBand[i]])
}
}
// check the position
var pos = e.GetPosition()
if (!pos) {
return
}
var holdAmount = 0
var holdPrice = 0
if (pos.length > 1) {
throw "long and short positions are checked at the same time!"
} else if (pos.length != 0) {
holdAmount = pos[0].Type == PD_LONG ? pos[0].Amount : -pos[0].Amount
holdPrice = pos[0].Price
}
if (e.state.preBar == -1) {
e.state.preBar = r[r.length - 1].Time
}
// check the signal
if (e.state.preBar != r[r.length - 1].Time) { // closing price model
if (holdAmount <= 0 && r[r.length - 3].Close < upBand[upBand.length - 3] && r[r.length - 2].Close > upBand[upBand.length - 2]) { // the closing price cross over the upBand
if (holdAmount < 0) { // hold a short positions, close them
Log(e.GetCurrency(), "close short positions", "#FF0000")
$.CoverShort(e, e.param.symbol, Math.abs(holdAmount))
c.add(kIndex + 4, {x: r[r.length - 2].Time, color: 'red', shape: 'flag', title: 'close', text: "close short positions"})
}
// open long positions
Log(e.GetCurrency(), "open long positions", "#FF0000")
$.OpenLong(e, e.param.symbol, 10)
c.add(kIndex + 4, {x: r[r.length - 2].Time, color: 'red', shape: 'flag', title: 'long', text: "open long positions"})
} else if (holdAmount >= 0 && r[r.length - 3].Close > downBand[downBand.length - 3] && r[r.length - 2].Close < downBand[downBand.length - 2]) { // the closing price cross down the downBand
if (holdAmount > 0) { // hold long positions, close them
Log(e.GetCurrency(), "close long positions", "#FF0000")
$.CoverLong(e, e.param.symbol, Math.abs(holdAmount))
c.add(kIndex + 4, {x: r[r.length - 2].Time, color: 'green', shape: 'flag', title: 'close', text: "close long positions"})
}
// open short positions
Log(e.GetCurrency(), "open short positions", "#FF0000")
$.OpenShort(e, e.param.symbol, 10)
c.add(kIndex + 4, {x: r[r.length - 2].Time, color: 'green', shape: 'flag', title: 'short', text: "open short positions"})
} else {
// close positions
if (holdAmount > 0 && (r[r.length - 2].Close <= holdPrice * (1 - e.param.stopLoss) || r[r.length - 2].Close <= midLine[midLine.length - 2])) { // Hold a long position, the closing price is less than or equal to the midline, stop loss at the opening price
Log(e.GetCurrency(), "trigger midline or stop loss, close long positions", "#FF0000")
$.CoverLong(e, e.param.symbol, Math.abs(holdAmount))
c.add(kIndex + 4, {x: r[r.length - 2].Time, color: 'green', shape: 'flag', title: 'close', text: "close long positions"})
} else if (holdAmount < 0 && (r[r.length - 2].Close >= holdPrice * (1 + e.param.stopLoss) || r[r.length - 2].Close >= midLine[midLine.length - 2])) { // Hold a short position, the closing price is greater than or equal to the midline, stop loss at the opening price
Log(e.GetCurrency(), "trigger midline or stop loss, close short positions", "#FF0000")
$.CoverShort(e, e.param.symbol, Math.abs(holdAmount))
c.add(kIndex + 4, {x: r[r.length - 2].Time, color: 'red', shape: 'flag', title: 'close', text: "close short positions"})
}
}
e.state.preBar = r[r.length - 1].Time
}
}
function main() {
var arrChartConfig = []
if (arrParam.length != exchanges.length) {
throw "Parameters and exchange objects do not match!"
}
var arrState = _G("arrState")
_.each(exchanges, function(e, index) {
if (e.GetName() != "Futures_Binance") {
throw "The exchange is not supported!"
}
e.param = arrParam[index]
e.state = {lastBarTime: 0, symbol: e.param.symbol, currency: e.GetCurrency()}
if (arrState) {
if (arrState[index].symbol == e.param.symbol && arrState[index].currency == e.GetCurrency()) {
Log("restore:", e.state)
e.state = arrState[index]
} else {
throw "The restored data does not match the current settings!"
}
}
e.state.preBar = -1 // initial setting -1
e.SetContractType(e.param.symbol)
Log(e.GetName(), e.GetLabel(), "set contracts:", e.param.symbol)
arrChartConfig.push(createChartConfig(e.GetCurrency(), e.param.atrPeriod, e.param.emaPeriod, index))
})
var chart = Chart(arrChartConfig)
chart.reset()
while (true) {
_.each(exchanges, function(e, index) {
process(e, index + index * 4, chart)
Sleep(500)
})
}
}
function onexit() {
// record e.state
var arrState = []
_.each(exchanges, function(e) {
arrState.push(e.state)
})
Log("record:", arrState)
_G("arrState", arrState)
}
Parâmetros da estratégia:
var params = '[{
"symbol" : "swap", // contract code
"period" : 86400, // K-line period, 86,400 seconds is a day
"stopLoss" : 0.07, // stop loss factor, 0.07 or 7%
"atrPeriod" : 10, // ATR indicator parameters
"emaPeriod" : 10, // EMA indicator parameters
"trackRatio" : 1, // upBand and downBand coefficients
"openRatio" : 0.1 // The reserved opening percentage, which is not supported for now
}, {
"symbol" : "swap",
"period" : 86400,
"stopLoss" : 0.07,
"atrPeriod" : 10,
"emaPeriod" : 10,
"trackRatio" : 1,
"openRatio" : 0.1
}]'
Screenshots de teste de retorno:
Código fonte da estratégia:https://www.fmz.com/strategy/339344
As estratégias são para backtesting e pesquisa de aprendizagem apenas. Por favor, modifique, otimize e consulte o bot real por si mesmo.