2004 Jul: btVolumeFlowIndicator.efs

ICE Data Services -

btVolumeFlowIndicator.efs   

File Name: btVolumeFlowIndicator.efs

Description:

Based on Volume Flow Indicator Performance by Markos Katsanos. This article appeared in the July 2004 issue of Stock & Commodities.

Formula Parameters:

  • VFI Length: 26
  • MA of VFI Length: 50
  • MA of VFI Type: EMA [SMA, EMA]
  • Back Test #: 1 [1,2,3,4]
    - 1 Indicator Level
    - 2 Divergence
    - 3 Break from base
    - 4 Indicator Direction
  • BT 2 Linear Regression Length: 120
  • BT 3 Linear Regression Length: 30
  • BT 4 SMA Length: 11
  • BT 4 LMA Length: 25
  • Initial Account Size: 10000

Notes:
The related article is copyrighted material. If you are not a subscriber of Stocks & Commodities, please visit www.traders.com.
The btVolumeFlowIndicator.efs is written for back testing with the Strategy Analyzer. The formula includes all four tests for the VFI as described in the article. When running the Strategy Analyzer, the user needs to configure the Formula Parameters in the Back Testing window (Tools-->Back Testing) before running the test. The formula also includes logic for taking short positions. However, if the user wants to exclude short trades they can uncheck the option in the Back Testing window, Allow Shorts, to exclude short trades for the test. On the chart, the bars where long positions were triggered are designated with a dark green background and the short positions are red. For those tests that have exit conditions, the bar where the exit signal is triggered will be have a gray background.

Download File:
btVolumeFlowIndicator.efs


EFS Code:

/*****************************************************************
Provided By : eSignal. (c) Copyright 2004
Study:        Back Testing Volume Flow Indicator by Markos Katsanos
Version:      1.0

5/4/2004

Formula Parameters:                 Default:
    VFI Length                      26
    MA of VFI Length                50
    MA of VFI Type                  EMA
       SMA
       EMA
    Back Test #                     1
       1 Indicator Level
       2 Divergence
       3 Break from base
       4 Indicator Direction
    BT 2 Linear Regression Length   120
    BT 3 Linear Regression Length   30
    BT 4 SMA Length                 11
    BT 4 LMA Length                 25
    Initial Account Size            10000

*****************************************************************/


function preMain() {
    setStudyTitle("Back Test Volume Flow Indicator ");
    setShowTitleParameters(false);
    setCursorLabelName("VFI", 0);
    setCursorLabelName("VFI MA", 1);
    setDefaultBarFgColor(Color.green, 0);
    setDefaultBarFgColor(Color.blue, 1);
    setDefaultBarThickness(2, 0);
    setDefaultBarThickness(2, 1);
    addBand(0, PS_SOLID, 2, Color.black, "zero");
    
    var fp0 = new FunctionParameter("nVFIlength", FunctionParameter.NUMBER);
    fp0.setName("VFI Length");
    fp0.setLowerLimit(1);
    fp0.setDefault(26);
    
    var fp1 = new FunctionParameter("nVFImaLen", FunctionParameter.NUMBER);
    fp1.setName("MA of VFI Length");
    fp1.setLowerLimit(1);
    fp1.setDefault(50);
    
    var fp1a = new FunctionParameter("sVFImaType", FunctionParameter.STRING);
    fp1a.setName("MA of VFI Type");
    fp1a.addOption("SMA");
    fp1a.addOption("EMA");
    fp1a.setDefault("EMA");
    
    var fp2 = new FunctionParameter("nTest", FunctionParameter.NUMBER);
    fp2.setName("Back Test #: ");
    fp2.setLowerLimit(1);
    fp2.setUpperLimit(4);
    fp2.addOption(1);   // Indicator Level
    fp2.addOption(2);   // Divergence
    fp2.addOption(3);   // Break from base
    fp2.addOption(4);   // Indicator Direction
    fp2.setDefault(1);
    
    var fp3 = new FunctionParameter("nLRlength", FunctionParameter.NUMBER);
    fp3.setName("BT 2 Linear Regression Length");
    fp3.setLowerLimit(1);
    fp3.setDefault(120);

    var fp4 = new FunctionParameter("nLRlength3", FunctionParameter.NUMBER);
    fp4.setName("BT 3 Linear Regression Length");
    fp4.setLowerLimit(1);
    fp4.setDefault(30);

    var fp5 = new FunctionParameter("nSMAlength", FunctionParameter.NUMBER);
    fp5.setName("BT 4 SMA Length");
    fp5.setLowerLimit(1);
    fp5.setDefault(11);

    var fp6 = new FunctionParameter("nLMAlength", FunctionParameter.NUMBER);
    fp6.setName("BT 4 LMA Length");
    fp6.setLowerLimit(1);
    fp6.setDefault(25);

    var fp7 = new FunctionParameter("nAcctStart", FunctionParameter.NUMBER);
    fp7.setName("Initial Account Size");
    fp7.setLowerLimit(1);
    fp7.setDefault(10000);
}


var nTyp = null;                // Current typical price
var nTyp1 = null;               // Previous typical price
var nTypChg = 0;                // Current typical price change
var vVol = null;                // Current volume
var nVolSum = 0;                // Cumulative volume sum
var nVolAdj = 0;                // Current adjusted volume
var nVolMA = null;              // Current Vol MA
var nVolMA1 = null;             // Previous Vol MA
var aTypPrice = null;           // Array of changes in typical price
var aVolume = null;             // Volume array
var VFI = null;                 // Current VFI
var aVFI = null;                // Array of VFI values for EMA calc
var aEMA = null;                // Array of VFI 3EMA values
var nTestNum = 1;               // Back Test Number
var bEdit = false;


// globals for EMA
var vEMA = new Array(5);
var vEMA1 = new Array(5);
var dPercent = new Array(0, 0, 0, 0, 0);
var bPrimed = new Array(false, false, false, false, false);
//[0] = VFI, [1] = (LRSI-LRS), [2] = ShortMA VFI, [3] = LongMA VFI, [4] = EMA of VFI

// globals for back testing
var nAccount = 10000;
var nEntry = null;
var bLong = false;
var bShort = false;
var bExit = false;
var aDivPrice = null;
var aDivVFI = null;
var aLRS_LRS1 = new Array(3);
var vDivergence = null;
var vDivergence1 = null;
var aVFItest3 = null;
var aLMA = null;
var aSMA = null;
var aClose = null;


function main(nVFIlength, nVFImaLen, sVFImaType, nTest, nLRlength, nLRlength3, 
            nSMAlength, nLMAlength, nAcctStart) {
    var nState = getBarState();
    var vInter = 0;
    var nCutoff = 0;
    var vMAofEMA = null;
    var dSum = 0;
    var i = 0;
    
    if (bEdit == false) {
        if (aTypPrice == null) aTypPrice = new Array(nVFIlength);
        if (aVolume == null) aVolume = new Array(nVFIlength);
        if (aEMA == null) aEMA = new Array(nVFImaLen);
        nTestNum = nTest;
        var sTestText = "";
        if (aVFI == null) {
            if (nTestNum == 1) {
                aVFI = new Array(7); 
                sTestText = "Test 1: Indicator Level";
            }
            if (nTestNum > 1) {
                aVFI = new Array(3);
                if (nTestNum == 2) {
                    sTestText = "Test 2: Divergence";                    
                }
            }
            if (nTestNum == 3) {
                addBand(-2, PS_SOLID, 2, Color.black, "zero");
                aVFItest3 = new Array(20);
                sTestText = "Test 3: Break From Base";                    
            }
            if (nTestNum == 4) {
                aSMA = new Array(nSMAlength);
                aLMA = new Array(nLMAlength);
                sTestText = "Test 4: Indicator Direction";
            }
        }
        if (nTestNum == 4) {
            setCursorLabelName("SMA VFI", 0);
            setCursorLabelName("LMA VFI", 1);
            setDefaultBarFgColor(Color.navy, 0);
        }
        drawTextAbsolute(1, 15, sTestText, Color.navy, null, 
            Text.RELATIVETOTOP|Text.RELATIVETOLEFT|Text.LEFT|Text.BOLD,
            null, 12, "TestNum");
        nAccount = nAcctStart;
        bEdit = true;
    }
    
    if (nState == BARSTATE_NEWBAR) {
        if (nTyp != null) {
            aTypPrice.pop();
            aTypPrice.unshift(nTypChg);
            nTyp1 = nTyp;
        }
        if (nVol != null) {
            aVolume.pop();
            aVolume.unshift(nVol);
        }
        nVolMA1 = nVolMA;
        nVolSum += nVolAdj;
        if (VFI != null) {
            aVFI.pop();
            aVFI.unshift(VFI);
        }
        if (vEMA[0] != null) {
            aEMA.pop();
            aEMA.unshift(vEMA[0]);
        }
    }
    
    nVol = volume();
    if (nVol == null) return;
    aVolume[0] = nVol;
    if (aVolume[nVFIlength-1] != null) {
        for (i = 0; i < nVFIlength; ++i) {
            dSum += aVolume[i];
        }
        nVolMA = dSum/nVFIlength;
    }
    
    nTyp = (high() + low() + close()) / 3;
    if (nTyp1 != null) {
        nTypChg = (Math.log(nTyp) - Math.log(nTyp1));
        aTypPrice[0] = nTypChg;
    }
    
    if (nVolMA == null || nVolMA1 == null) return;
    
    if (aTypPrice[nVFIlength-1] != null) {
        vInter = StDev(nVFIlength);
        nCutoff = (.2 * vInter * close());
    } else {
        return;
    }

    nVolAdj = nVol;
    //Minimal Change Cutoff
    if ((nTyp - nTyp1) >= 0 && (nTyp - nTyp1) < nCutoff) nVolAdj = 0;
    if ((nTyp - nTyp1) < 0 && (nTyp - nTyp1) > -nCutoff) nVolAdj = 0;
    // Volume curtailment
    if (nVolAdj > (2.5*nVolMA1)) nVolAdj = (2.5*nVolMA1);
    
    if (nTyp - nTyp1 < 0) nVolAdj *= -1;
    VFI = ((nVolSum + nVolAdj) / nVolMA1);
    aVFI[0] = VFI;
    
    if (aVFI[(aVFI.length)-1] != null) {
        vEMA[0] = EMA(0, VFI, aVFI.length, aVFI);
        aEMA[0] = vEMA[0];
    }
    if (aEMA[nVFImaLen-1] != null) {
        if (sVFImaType == "SMA") {
            dSum = 0;
            i = 0;
            for(i = 0; i < nVFImaLen; ++i) {
                dSum += aEMA[i];
            }
            vMAofEMA = dSum/nVFImaLen;
        } else if (sVFImaType == "EMA") {
            vEMA[4] = EMA(4, aEMA[0], aEMA.length, aEMA);
            vMAofEMA = vEMA[4];
        }
    }

    if (vEMA[0] != null && nTestNum != 4) {
        var nLine = 0;
        if (nTestNum == 3) nLine = -2
        if (vEMA[0] > nLine) {
            setBarFgColor(Color.green, 0);
        } else {
            setBarFgColor(Color.red, 0);
        }
    } 
    
    // Back Testing Section
    if (getCurrentBarIndex() < 0 && vEMA != null && vEMA1 != null) { // processing historical data for back testing
        // draw entry signals
        if (bLong == true) {
            setBarBgColor(Color.darkgreen);
            //drawShapeRelative(0, vEMA1[0], Shape.UPARROW, null, 
            //    Color.khaki, Shape.TOP|Shape.ONTOP);
        }
        if (bShort == true) {
            setBarBgColor(Color.maroon);
            //drawShapeRelative(0, vEMA1[0], Shape.DOWNARROW, null, 
            //    Color.khaki, Shape.BOTTOM|Shape.ONTOP);
        }
        if (bExit == true) {
            setBarBgColor(Color.lightgrey);
            //drawShapeRelative(0, vEMA1[0], Shape.DIAMOND, null, 
            //    Color.khaki, Shape.BOTTOM|Shape.ONTOP);
        }
        
        bLong = false;
        bShort = false;
        bExit = false;
        
        var nLotSize = Math.floor(nAccount/open(1));
        
        // Test 1   Indicator Level
        if (nTestNum == 1) {
            if (vEMA[0] > 0 && vEMA1[0] <= 0 && Strategy.isLong() == false) { // long
                if (Strategy.isShort() == true) {
                    nAccount += ( (vEntry-open(1)) * (-Strategy.getPositionSize()) );
                    Strategy.doCover("Close Short", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                    nLotSize = Math.floor(nAccount/open(1))
                }
                bLong = true;
                vEntry = open(1);
                Strategy.doLong("Crossing Up", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
            } else if (vEMA[0] < 0 && vEMA1[0] >= 0 && Strategy.isShort() == false) { // short
                if (Strategy.isLong() == true) {
                    nAccount += ( (open(1)-vEntry) * (Strategy.getPositionSize()) );
                    Strategy.doSell("Close Long", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                    nLotSize = Math.floor(nAccount/open(1))
                }
                bShort = true;
                vEntry = open(1);
                Strategy.doShort("Crossing Down", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
            }
        }
        // Test 2   Divergence
        if (nTestNum == 2) {
            if (aDivPrice == null) aDivPrice = new Array(nLRlength);
            if (aDivVFI == null) aDivVFI = new Array(nLRlength);
            
            if (vDivergence != null) vDivergence1 = vDivergence;
            // VFI1 conversion = VFI + Math.abs(Lowest(VFI)) + 10;
            var VFI1 = (VFI + Math.abs(Lowest()) + 10).toFixed(2)*1;
            var LRS, LRS1;
            
            aDivPrice.pop();
            aDivPrice.unshift(close());
            aDivVFI.pop();
            aDivVFI.unshift(VFI1);

            if (aDivPrice[nLRlength-1] != null && aDivVFI[nLRlength-1] != null) {
                LRS = (LinearRegressionSlope(aDivPrice) / aDivPrice[nLRlength-1]);
                LRS1 = (LinearRegressionSlope(aDivVFI) / aDivVFI[nLRlength-1]);
                aLRS_LRS1.pop();
                aLRS_LRS1.unshift((LRS1 - LRS));
                if (aLRS_LRS1[2] != null) {
                    vEMA[1] = EMA(1, aLRS_LRS1[0], 3, aLRS_LRS1);
                    vDivergence = vEMA[1] * 100;
                    if (vDivergence != null) {
                        if (Strategy.isInTrade() == false) {
                            if (vDivergence1 > 100 && vDivergence < vDivergence1 && LRS1 > 0) {  // Long entry
                                bLong = true;
                                vEntry = open(1);
                                Strategy.doLong("Long Divergence", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
                            } else if (vDivergence1 < -100 && vDivergence > vDivergence1 && LRS1 < 0) {  // Short entry
                                bShort = true;
                                vEntry = open(1);
                                Strategy.doShort("Short Divergence", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
                            }
                        } else if (Strategy.isLong()) { // exit long
                            if (vDivergence < 0 && LRS1 < 0) {
                                nAccount += ( (open(1)-vEntry) * (Strategy.getPositionSize()) );
                                bExit = true;
                                Strategy.doSell("Exit Long", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                            }
                        } else if (Strategy.isShort()) {  // exit short
                            if (vDivergence > 0 && LRS1 > 0) {
                                nAccount += ( (vEntry-open(1)) * (-Strategy.getPositionSize()) );
                                bExit = true;
                                Strategy.doCover("Exit Short", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                            }
                        }
                    }
                }
            }
        }
        // Test 3
        if (nTestNum == 3) {
            if (aClose == null) aClose = new Array(nLRlength3);
            var bC1Up = false;  // Condition 1
            var bC1Dn = false;
            var bC2Up = false;  // Condition 2
            var bC2Dn = false;
            var bC3Up = false;  // Condition 3
            var bC3Dn = false;
            var bC4Up = false;  // Condition 4
            var bC4Dn = false;
            // Condition 1
            if (vEMA[0] > -2) {
                bC1Up = true;
            } else if (vEMA[0] < -2) {
                bC1Dn = true;
            }
            // Condition 2
            aVFItest3.pop();
            aVFItest3.unshift(vEMA[0]);
            var nLRangle = null;
            if (aVFItest3[19] != null) {
                nLRangle = LinearRegressionSlope(aVFItest3)/100;
                nLRangle = (Math.atan(nLRangle) / (Math.PI/180));
            }
            if (nLRangle != null) {
                if (nLRangle > 0) bC2Up = true;
                if (nLRangle < 0) bC2Dn = true;
            }
            // Condition 3
            if (vEMA[0] > vMAofEMA) bC3Up = true;
            if (vEMA[0] < vMAofEMA) bC3Dn = true;
            // Condition 4
            var LRS = null;
            aClose.pop();
            aClose.unshift(close());
            if (aClose[nLRlength3-1] != null) {
                LRS = LinearRegressionSlope(aClose);
                if (LRS < (0.006*aClose[nLRlength3-1]) && LRS > 0) bC4Up = true;
                if (LRS > (-0.006*aClose[nLRlength3-1]) && LRS < 0) bC4Dn = true;
            }            
            if (Strategy.isInTrade() == false) {
                if (bC1Up == true && bC2Up == true && bC3Up == true && bC4Up == true) {  // Long entry
                    bLong = true;
                    vEntry = open(1);
                    Strategy.doLong("Long Break", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
                } else if (bC1Dn == true && bC2Dn == true && bC3Dn == true && bC4Up == true) {  // Short entry
                    bShort = true;
                    vEntry = open(1);
                    Strategy.doShort("Short Break", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
                }
            } else if (Strategy.isLong()) { // exit long
                if (nLRangle <= -40 || vEMA[0] < -2) {
                    nAccount += ( (open(1)-vEntry) * (Strategy.getPositionSize()) );
                    bExit = true;
                    Strategy.doSell("Exit Long", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                }
            } else if (Strategy.isShort()) {  // exit short
                if (nLRangle >= 40 || vEMA[0] > -2) {
                    nAccount += ( (vEntry-open(1)) * (-Strategy.getPositionSize()) );
                    bExit = true;
                    Strategy.doCover("Exit Short", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                }
            }
        }
        // Test 4
        if (nTestNum == 4) {
            aSMA.pop();
            aSMA.unshift(vEMA[0]);
            aLMA.pop();
            aLMA.unshift(vEMA[0]);
            if (aSMA[nSMAlength-1] != null) {
                vEMA[2] = EMA(2, vEMA[0], nSMAlength, aSMA);
            }
            if (aLMA[nLMAlength-1] != null) {
                vEMA[3] = EMA(3, vEMA[0], nLMAlength, aLMA);
            }
            if (vEMA[2] > vEMA[3] && vEMA1[2] < vEMA1[3] && Strategy.isLong() == false) { // long
                if (Strategy.isShort() == true) {
                    nAccount += ( (vEntry-open(1)) * (-Strategy.getPositionSize()) );
                    Strategy.doCover("Close Short", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                    nLotSize = Math.floor(nAccount/open(1))
                }
                bLong = true;
                vEntry = open(1);
                Strategy.doLong("Crossing Up", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
            } else if (vEMA[2] < vEMA[3] && vEMA1[2] > vEMA1[3] && Strategy.isShort() == false) { // short
                if (Strategy.isLong() == true) {
                    nAccount += ( (open(1)-vEntry) * (Strategy.getPositionSize()) );
                    Strategy.doSell("Close Long", Strategy.MARKET, Strategy.NEXTBAR, Strategy.getPositionSize());
                    nLotSize = Math.floor(nAccount/open(1))
                }
                bShort = true;
                vEntry = open(1);
                Strategy.doShort("Crossing Down", Strategy.MARKET, Strategy.NEXTBAR, nLotSize);
            }
        }
    }
    // End of Back Testing

    if (nTestNum != 4) {
        return new Array(vEMA[0], vMAofEMA);
    } else {
        return new Array(vEMA[2], vEMA[3]);
    }
}



/*********************
**********************
****** Functions *****
**********************
**********************/

function StDev(nLength) {
    var sumX = 0;
    var sumX2 = 0;
    for (i = 0; i < nLength; ++i) {
        sumX += aTypPrice[i];
        sumX2 += (aTypPrice[i] * aTypPrice[i])
    }
    var meanX = (sumX/nLength);
    var stdev = Math.sqrt((sumX2/nLength) - (meanX*meanX));

    return stdev;
}

function EMA(nNum, nItem, nLength, aArray) {
    var nBarState = getBarState();
    var dSum = 0.0;

    if(nBarState == BARSTATE_ALLBARS || bPrimed[nNum] == false) {
        dPercent[nNum] = (2.0 / (nLength + 1.0));
        bPrimed[nNum] = false;
    }

    if (nBarState == BARSTATE_NEWBAR) {
        vEMA1[nNum] = vEMA[nNum];
    }

    if(bPrimed[nNum] == false) {
        for(i = 0; i < nLength; i++) {
            dSum += aArray[i];
        }
        bPrimed[nNum] = true;
        return (dSum / nLength);
    } else {
        return (((nItem - vEMA1[nNum]) * dPercent[nNum]) + vEMA1[nNum]);
    }
}

function Lowest() {
    var vLowVFI = VFI;
    for (var i = 0; i < aVFI.length; ++i) {
        if (aVFI[i] != null) {
            vLowVFI = Math.min(vLowVFI, aVFI[i]);
        }
    }
    
    return vLowVFI;
}

function LinearRegressionSlope(aArray, nLength) {
    var nIndex = getCurrentBarIndex();
    var i = 0;

    if (nLength == null) nLength = aArray.length;
    
    // y = Ax + B;
    // A = SUM( (x-xAVG)*(y-yAVG) ) / SUM( (x-xAVG)^2 )
    // A = slope
    // B = yAVG - (A*xAVG);
    
    if (aArray[nLength-1] != null) {
        var xSum = 0;
        var ySum = 0;
        i = 0;
        for (i = 0; i < nLength; ++i) {
            xSum += i;
            ySum += aArray[i];
        }
        var xAvg = xSum/nLength;
        var yAvg = ySum/nLength;
        var aSum1 = 0;
        var aSum2 = 0;
        i = 0;
        for (i = 0; i < nLength; ++i) {
            aSum1 += (i-xAvg) * (aArray[i]-yAvg); 
            aSum2 += (i-xAvg)*(i-xAvg);
        }
        var A = (aSum1 / aSum2);
        var B = yAvg - (A*xAvg);
    }
    
    var vSlope = (-A.toFixed(8)*100);
    
    return vSlope;
}