In the last article Teach you to write strategies -- transplant a MyLanguage strategy, a simple MyLanguage strategy has been tested for transplantation. If it is a more complex MyLanguage, how can it be transplanted into a JavaScript language strategy? What skills are there?

Let's look at the strategy to be transplanted first:

(*backtest
start: 2019-05-01 00:00:00
end: 2019-11-12 00:00:00
period: 1d
exchanges: [{"eid":"Futures_OKCoin","currency":"BTC_USD"}]
args: [["SlideTick",10,126961],["ContractType","quarter",126961]]
*)

N1:=10;
N2:=21;
AP:=(HIGH+LOW+CLOSE)/3;
ESA:=EMA(AP,N1);
D:=EMA(ABS(AP-ESA),N1);
CI:=(AP-ESA)/(0.015*D);
TCI:=EMA(CI,N2);
WT1:TCI;
WT2:SMA(WT1,4,1);
AA:=CROSS(WT1,WT2);
BB:=CROSSDOWN(WT1,WT2);
REF(AA,1),BPK;
REF(BB,1),SPK;

The (* backtest... *) at the beginning of the MyLanguage strategy is the configuration code for backtesting settings. To facilitate comparison, a unified backtesting configuration is set. This strategy is also a random one, which is not too complex (more complex than that in the last article). It is a representative strategy. To transplant a MyLanguage strategy, you should look at the whole strategy first. The code of the strategy is concise, and you can have a certain understanding of the overall strategy. For this strategy, we have seen that several indicator functions EMA, SMA have been used:

Build a wheel first

  • EMA
    The indicator function, there are ready-made indicator library functions directly available in the FMZ platform when writing strategies in JavaScript language. That is: TA.MA.
  • SMA
    What we need to do is the SMA indicator, which we found in the TA library of FMZ does not support the SMA indicator function, and there are differences between the SMA indicator in the talib library and the one in the MyLanguage.

As we can see, the parameters section has a weights parameter in addition to the period parameter.

The SMA indicator function in the talib library in the FMZ API documentation is described as:

It can be seen that talib.SMA is a simple moving average indicator.
In this way, we can only implement an SMA by ourselves. As a developer using the JavsScript language to write strategies, this is also one of the necessary skills. After all, if there is no ready-made wheel, the program still needs to run, just build one.

To tell the truth, there is not much research on indicators and so on. Generally, people search for information if they don't understand it. For SMA to find these:

It seems that the algorithm process of this theory is quite reliable, and the implementation is as follows:

function SMA (arr, n, m) {
    var sma = []
    var currSMA = null
    for (var i = 0; i < arr.length; i++) {
        if (arr[i] && !isNaN(arr[i])) {
            if (!currSMA) {
                currSMA = arr[i]
                sma.push(currSMA)
                continue
            }  

            // [M*C2+(N-M)*S1]/N
            currSMA = (m * arr[i] + (n - m) * currSMA) / n
            sma.push(currSMA)
        } else {
            sma.push(NaN)
        }
    }  

    return sma
}

Write populated sections

The strategy framework uses the same framework as in the article Teach you to write strategies -- transplant a MyLanguage strategy and mainly filled in two parts:

First, do ticker data processing and index calculation.

Let's take this part of the MyLanguage one sentence at a time, function by function:

  • 1. AP:=(HIGH+LOW+CLOSE)/3;

It can be understood that the highest price, lowest price and closing price of each BAR in the K line data should be added and then divided by 3 to calculate the average value, and then saved as an array, corresponding to each BAR one by one.
It can be processed as follows:

function CalcAP (r) {   // AP:=(HIGH+LOW+CLOSE)/3;
    var arrAP = []      // Declare an empty array

    for (var i = 0; i < r.length; i++) {      // r is the incoming K-line data, which is an array, use for to traverse this array.
        v = (r[i].High + r[i].Low + r[i].Close) / 3      // Calculate the average value.
        arrAP.push(v)                                    // Add to the end of the arrAP array, the end is the first when arrAP is empty.
    }  

    return arrAP     // Returns this average array, i.e., the AP calculated in the MyLanguage 
}

This function can be called in the main loop OnTick function, for example:

// Calculation of indicators
// AP
var ap = CalcAP(records)
  • 2. After the AP calculation is completed, proceed to calculate ESA:=EMA(AP,N1);:

Here, we will use the data of AP calculated in the previous step to calculate the ESA. In fact, the ESA is the "exponential moving average" of the AP, that is, the EMA indicator. So we will use AP as the data and N1 as the parameter of the EMA indicator to calculate the EMA indicator.

function CalcESA (ap, n1) {   // ESA:=EMA(AP,N1);
    if (ap.length <= n1) {    // If the AP length is less than the indicator parameter, valid data cannot be calculated. At this time, let the function return false.
        return false
    }  

    return TA.EMA(ap, n1)
}
  1. D:=EMA(ABS(AP-ESA),N1);

Use the calculated APESA to calculate the data D.
The code comments here can be read for some tips on how to calculate the indicators.

function CalcD (ap, esa, n1) {    // D:=EMA(ABS(AP-ESA),N1);
    var arrABS_APminusESA = []
    if (ap.length != esa.length) {
        throw "ap.length != esa.length"
    }  

    for (var i = 0; i < ap.length; i++) {
        // When calculating the value of the indicator, it is necessary to determine the validity of the data, because the first few EMA calculations may be the beginning of the array of data is NaN, or null.
        // So it must be judged that the data involved in the calculation are all valid values to proceed, and if there are any invalid values, they are filled with NaN to arrABS_APminusESA.
        // The data thus calculated, each position corresponds to the previous data one by one, without misalignment.
        if (ap[i] && esa[i] && !isNaN(ap[i]) && !isNaN(esa[i])) {
            v = Math.abs(ap[i] - esa[i])     // According to ABS(AP-ESA), the specific value is calculated and put into the arrABS_APminusESA array.
            arrABS_APminusESA.push(v)
        } else {
            arrABS_APminusESA.push(NaN)
        }
    }  

    if (arrABS_APminusESA.length <= n1) {
        return false
    }  

    return TA.EMA(arrABS_APminusESA, n1)    // Calculate the EMA indicator of the array arrABS_APminusESA and get the data D (array structure).
}
  1. CI:=(AP-ESA)/(0.015*D);
    The calculation method is similar to step 1, and the code is released directly.
function CalcCI (ap, esa, d) {    // CI:=(AP-ESA)/(0.015*D);
    var arrCI = []
    if (ap.length != esa.length || ap.length != d.length) {
        throw "ap.length != esa.length || ap.length != d.length"
    }
    for (var i = 0; i < ap.length; i++) {
        if (ap[i] && esa[i] && d[i] && !isNaN(ap[i]) && !isNaN(esa[i]) && !isNaN(d[i])) {
            v = (ap[i] - esa[i]) / (0.015 * d[i])
            arrCI.push(v)
        } else {
            arrCI.push(NaN)
        }
    }  

    if (arrCI.length == 0) {
        return false
    }  

    return arrCI
}
  • TCI:=EMA(CI,N2);
    Just calculate the EMA indicator for the CI array.
function CalcTCI (ci, n2) {   // TCI:=EMA(CI,N2);
    if (ci.length <= n2) {
        return false
    }  

    return TA.EMA(ci, n2)
}
  • WT2:SMA(WT1,4,1);

In this last step, the SMA function of the wheel we built before is used.

function CalcWT2 (wt1) {    // WT2:SMA(WT1,4,1);
    if (wt1.length <= 4) {
        return false 
    }  

    return SMA(wt1, 4, 1)   // The SMA indicator for wt1 is calculated by using our own implementation of the SMA function.
}

The transplating of trading signals is very simple.

AA:=CROSS(WT1,WT2);
BB:=CROSSDOWN(WT1,WT2);
REF(AA,1),BPK;
REF(BB,1),SPK;

After reading these codes of MyLanguage, we can see that the Golden Cross and Bearish Crossover of WT1 and WT2 are used as opening conditions. It should be noted that the previous cross signal is used.
Using the MyLanguage strategy backtest directly, we observe that:

It can be seen from the observation of the actual operation of the MyLanguage strategy that when a signal is detected at the opening position, it is actually to detect whether the position of the BAR at the opening point counting 2 BARs forward is a Golden Cross. The above figure clearly shows that:

The filling code of the signal detection part can be written as:

if ((_State == IDLE || _State == SHORT) && wt1[wt1.length - 4] < wt2[wt2.length - 4] && wt1[wt1.length - 3] > wt2[wt2.length - 3]) {
    if (_State == IDLE) {
        _State = OPENLONG
        Log("OPENLONG")    // test
    }
    if (_State == SHORT) {
        _State = COVERSHORT
        Log("COVERSHORT")  // test
    }
    isOK = false  
}

if ((_State == IDLE || _State == LONG) && wt1[wt1.length - 4] > wt2[wt2.length - 4] && wt1[wt1.length - 3] < wt2[wt2.length - 3]) {
    if (_State == IDLE) {
        _State = OPENSHORT
        Log("OPENSHORT")  // test
    }
    if (_State == LONG) {
        _State = COVERLONG
        Log("COVERLONG")  // test
    }
    isOK = false   
}

Here you can think about why the SPK and BPK instructions of the MyLanguage can be implemented with the above code.

Backtest

Backtest configuration:

Backtest in MyLanguage:

Backtest in JavaScript version:

The code at the beginning of the OnTick function is used to make the backtesting faster. It is used to run the strategy based on the Bar model. If you are interested, you can analyze it in detail.

function OnTick(){
    // The ticker processing part of the driving strategy.
    var records = _C(exchange.GetRecords)
    if (records[records.length - 1].Time == preTime) {
        if (isOK) {
            Sleep(500)
            return 
        }
    } else {
        preTime = records[records.length - 1].Time
    }
    ...
    ..
    .

The complete teaching strategy code:
https://www.fmz.com/strategy/174457

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *