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; }