Signature Description Parameters
#include <DataFrame/DataFrameFinancialVisitors.h>

template<typename T, typename I = unsigned long>
struct KeltnerChannelsVisitor;

// -------------------------------------

template<typename T, typename I = unsigned long>
using kch_v = KeltnerChannelsVisitor<T, I>;
        
This is a “single action visitor”, meaning it is passed the whole data vector in one call and you must use the single_act_visit() interface.

This visitor calculates the rolling values of Keltner channels. It requires 3 input columns in the order of low, high, close.
The result() method returns the upper band.

Keltner Channels are volatility-based envelopes set above and below an exponential moving average. This indicator is similar to Bollinger Bands, which use the standard deviation to set the bands. Instead of using the standard deviation, Keltner Channels use the Average True Range (ATR) to set channel distance. The channels are typically set two Average True Range values above and below the 20-day EMA. The exponential moving average dictates direction and the Average True Range sets channel width. Keltner Channels are a trend following indicator used to identify reversals with channel breakouts and channel direction. Channels can also be used to identify overbought and oversold levels when the trend is flat.
There are 3 other methods:
    get_result()      // It returns the upper band
    get_upper_band()  // It returns the upper band
    get_lower_band()  // It returns the lower band
        
    explicit
    KeltnerChannelsVisitor(size_t roll_period = 20, T band_multiplier = 2.0)

    roll_period: Rolling period
    band_multiplier: Band multiplier of upper and lower
        
T: Column data type
I: Index type
static void test_KeltnerChannelsVisitor()  {

    std::cout << "\nTesting KeltnerChannelsVisitor{  } ..." << std::endl;

    typedef StdDataFrame<std::string> StrDataFrame;

    StrDataFrame    df;

    try  {
        df.read("data/SHORT_IBM.csv", io_format::csv2);

        kch_v<double, std::string>  kch;

        df.single_act_visit<double, double, double>("IBM_Low", "IBM_High", "IBM_Close", kch);

        assert(kch.get_result().size() == 1721);
        assert(std::abs(kch.get_upper_band()[0] - 189.93) < 0.0001);
        assert(std::abs(kch.get_upper_band()[12] - 193.3376) < 0.0001);
        assert(std::abs(kch.get_upper_band()[20] - 187.5627) < 0.0001);
        assert(std::abs(kch.get_upper_band()[25] - 184.0657) < 0.0001);
        assert(std::abs(kch.get_upper_band()[35] - 186.5203) < 0.0001);
        assert(std::abs(kch.get_upper_band()[1720] - 123.722) < 0.0001);
        assert(std::abs(kch.get_upper_band()[1712] - 130.6271) < 0.0001);
        assert(std::abs(kch.get_upper_band()[1707] - 130.4991) < 0.0001);

        assert(kch.get_lower_band().size() == 1721);
        assert(std::abs(kch.get_lower_band()[0] - 181.13) < 0.0001);
        assert(std::abs(kch.get_lower_band()[12] - 181.8944) < 0.0001);
        assert(std::abs(kch.get_lower_band()[20] - 175.9381) < 0.0001);
        assert(std::abs(kch.get_lower_band()[25] - 173.3489) < 0.0001);
        assert(std::abs(kch.get_lower_band()[35] - 175.6794) < 0.0001);
        assert(std::abs(kch.get_lower_band()[1720] - 110.3163) < 0.0001);
        assert(std::abs(kch.get_lower_band()[1712] - 116.7584) < 0.0001);
        assert(std::abs(kch.get_lower_band()[1707] - 117.0264) < 0.0001);
    }
    catch (const DataFrameError &ex)  {
        std::cout << ex.what() << std::endl;
    }
}
C++ DataFrame