Recientemente, algunos usuarios de nuestra plataforma están muy ansiosos por portar una estrategia de Mylanguage a una estrategia de JavaScript, para que muchas ideas de optimización puedan añadirse de manera flexible. Incluso desean extender una estrategia a una versión de múltiples símbolos. Debido a que las estrategias de Mylanguage son generalmente estrategias de tendencia, y muchas se ejecutan en un modelo de precio cercano. Esas estrategias solicitan una interfaz API de la plataforma con poca frecuencia, lo que es más adecuado para la portación a una versión de estrategia de múltiples símbolos. En el artículo, tomamos una estrategia de Mylanguage simple como ejemplo y la portamos a una versión simple del lenguaje JavaScript. El propósito principal es enseñar, backtest y investigación. Si desea ejecutar una estrategia, es posible que necesite agregar algunos detalles (como la cantidad del pedido, precisión, cantidad del pedido, estado del pedido por control de activos, relación de información y bot, etc.), también necesita ejecutar una prueba de tick 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
C>=SKPRICE*(1+SLOSS*0.01),BP;
C<=BKPRICE*(1-SLOSS*0.01),SP;
AUTOFILTER;
La lógica de la estrategia es muy simple. Primero, según los parámetros, calcular ATR, y luego calcular los valores promedio de los precios más altos, más bajos, cerrados y abiertos de todos los BAR de la línea K, por los que se calculará el indicador EMA. Finalmente, sobre la base de ATR y la relación N en los parámetros, calcular upBand y downBand.
La posición abierta y la posición inversa se basan en el precio de cierre que atraviesa la banda ascendente y la banda descendente. Cuando el precio de cierre alcanza la línea media, posición cerrada; cuando el precio de cierre alcanza el precio de stop loss, posición cerrada (según SLOSS para stop loss; cuando SLOSS es 1, significa 0,01, es decir, 1%). La estrategia se ejecuta en el modelo de precio de cierre.
Una vez entendidos los requisitos estratégicos y los pensamientos de Mylanguage, podemos empezar a trasladar.
El código del prototipo de estrategia no es demasiado largo, sólo de 1 a 200 líneas. Para que usted pueda estudiar convenientemente las ideas de escritura de estrategia, escribo directamente las observaciones en el código de estrategia.
// parse params, from string to object
var arrParam = JSON.parse(params)
// the function creates the chart configuration
function createChartConfig(symbol, atrPeriod, emaPeriod, index) { // symbol: trading pair; atrPeriod: ATR parameter period; emaPeriod: EMA parameter period; index: index of the corresponding exchange object
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, such as exchanges[0] ... ; kIndex is the data series of K-line data in the chart; 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 K-line data length is insufficient, return
return
}
// calculate ATR indicator
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 indicator
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])
})
// plot
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]])
}
}
// detect position
var pos = e.GetPosition()
if (!pos) {
return
}
var holdAmount = 0
var holdPrice = 0
if (pos.length > 1) {
throw "Long and short positions are detected simultaneously!"
} 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
}
// detect signal
if (e.state.preBar != r[r.length - 1].Time) { // close price model
if (holdAmount <= 0 && r[r.length - 3].Close < upBand[upBand.length - 3] && r[r.length - 2].Close > upBand[upBand.length - 2]) { // close price up cross the upBand
if (holdAmount < 0) { // holding short, close position
Log(e.GetCurrency(), "close short position", "#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 position"})
}
// open long
Log(e.GetCurrency(), "open long position", "#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 position"})
} else if (holdAmount >= 0 && r[r.length - 3].Close > downBand[downBand.length - 3] && r[r.length - 2].Close < downBand[downBand.length - 2]) { // close price down cross the downBand
if (holdAmount > 0) { // holding long, close position
Log(e.GetCurrency(), "close long position", "#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 position"})
}
// open short
Log(e.GetCurrency(), "open short position", "#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 position"})
} else {
// close position
if (holdAmount > 0 && (r[r.length - 2].Close <= holdPrice * (1 - e.param.stopLoss) || r[r.length - 2].Close <= midLine[midLine.length - 2])) { // if holding long position, close price is equal to or less than midline, stop loss according to open position price
Log(e.GetCurrency(), "if midline is triggered or stop loss, close long position", "#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 position"})
} else if (holdAmount < 0 && (r[r.length - 2].Close >= holdPrice * (1 + e.param.stopLoss) || r[r.length - 2].Close >= midLine[midLine.length - 2])) { // if holding short position, close price is equal to or more than midline, stop loss according to open position price
Log(e.GetCurrency(), "if midline is triggered or stop loss, close short position", "#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 position"})
}
}
e.state.preBar = r[r.length - 1].Time
}
}
function main() {
var arrChartConfig = []
if (arrParam.length != exchanges.length) {
throw "The parameter and the exchange object do not match!"
}
var arrState = _G("arrState")
_.each(exchanges, function(e, index) {
if (e.GetName() != "Futures_Binance") {
throw "The platform 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("Recover:", e.state)
e.state = arrState[index]
} else {
throw "The recovered data and the current setting do not match!"
}
}
e.state.preBar = -1 // initially set -1
e.SetContractType(e.param.symbol)
Log(e.GetName(), e.GetLabel(), "Set contract:", 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 de la estrategia:
var params = '[{
"symbol" : "swap", // contract code
"period" : 86400, // K-line period; 86400 seconds indicates 1 day
"stopLoss" : 0.07, // ratio of stoploss; 0.07 means 7%
"atrPeriod" : 10, // ATR indicator parameter
"emaPeriod" : 10, // EMA indicator parameter
"trackRatio" : 1, // ratio of upBand or downBand
"openRatio" : 0.1 // ratio of reserved open position (temporarily not supported)
}, {
"symbol" : "swap",
"period" : 86400,
"stopLoss" : 0.07,
"atrPeriod" : 10,
"emaPeriod" : 10,
"trackRatio" : 1,
"openRatio" : 0.1
}]'
Prueba de retroceso
Código fuente de la estrategia:https://www.fmz.com/strategy/339344
La estrategia sólo se utiliza para la comunicación y el estudio; para el uso práctico, usted necesita modificar, ajustar y optimizar por sí mismo.