From f46ecb20bd96503491784c02eca08c065ca1af10 Mon Sep 17 00:00:00 2001 From: Ilya Pikulin Date: Sun, 24 Mar 2019 15:42:37 +0300 Subject: [PATCH] Chandelier Exit indicator --- indicators.tcl | 5 +- indicators/ce.c | 357 +++++++++++++++++++++++++++++++++++++++++++++++ indicators/max.c | 22 +++ indicators/min.c | 22 +++ tests/extra.txt | 9 ++ 5 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 indicators/ce.c diff --git a/indicators.tcl b/indicators.tcl index 5dbf474..916a911 100644 --- a/indicators.tcl +++ b/indicators.tcl @@ -138,8 +138,8 @@ lappend indicators [list indicator "Rate of Change Ratio" rocr 1 1 1 {real} {per #Math functions lappend indicators [list math "Lag" lag 1 1 1 {real} {period} {lag}] -lappend indicators [list math "Maximum In Period" max 1 1 1 {real} {period} {max}] -lappend indicators [list math "Minimum In Period" min 1 1 1 {real} {period} {min}] +lappend indicators [list math "Maximum In Period" max 1 1 1 {real} {period} {max} {ref}] +lappend indicators [list math "Minimum In Period" min 1 1 1 {real} {period} {min} {ref}] lappend indicators [list math "Sum Over Period" sum 1 1 1 {real} {period} {sum}] lappend indicators [list math "Standard Deviation Over Period" stddev 1 1 1 {real} {period} {stddev}] lappend indicators [list math "Standard Error Over Period" stderr 1 1 1 {real} {period} {stderr}] @@ -154,6 +154,7 @@ lappend indicators [list overlay "Weighted Close Price" wcprice 3 0 1 {high low #Volatility lappend indicators [list indicator "Average True Range" atr 3 1 1 {high low close} {period} {atr} {stream ref}] +lappend indicators [list indicator "Chandelier Exit" ce 3 2 2 {high low close} {period coef} {ce_high ce_low} {stream ref}] lappend indicators [list indicator "Normalized Average True Range" natr 3 1 1 {high low close} {period} {natr}] lappend indicators [list indicator "True Range" tr 3 0 1 {high low close} {} {tr}] lappend indicators [list indicator "Annualized Historical Volatility" volatility 1 1 1 {real} {period} {volatility}] diff --git a/indicators/ce.c b/indicators/ce.c new file mode 100644 index 0000000..fd0423a --- /dev/null +++ b/indicators/ce.c @@ -0,0 +1,357 @@ +/* + * Tulip Indicators + * https://tulipindicators.org/ + * Copyright (c) 2010-2019 Tulip Charts LLC + * Lewis Van Winkle (LV@tulipcharts.org) + * + * This file is part of Tulip Indicators. + * + * Tulip Indicators is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Tulip Indicators is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Tulip Indicators. If not, see . + * + */ + +#include "../indicators.h" +#include "truerange.h" + +#include + +int ti_ce_start(TI_REAL const *options) { + return (int)options[0]-1; +} + +/* Name: Chandelier Exit + * Sources: + * - Alexander Elder. Come Into My Trading Room, 2002, pp. 180-181. CE, original description + * ISBN: 9780471225348 + * - J. Welles Wilder. New Concepts in Technical Trading Systems, 1978, pp. 21-23. ATR, original description + * ISBN: 9780894590276 + */ + +/*********** Plain implementation ************/ + +int ti_ce(int size, TI_REAL const *const *inputs, TI_REAL const *options, TI_REAL *const *outputs) { + TI_REAL const *high = inputs[0]; + TI_REAL const *low = inputs[1]; + TI_REAL const *close = inputs[2]; + const int period = options[0]; + const TI_REAL coef = options[1]; + TI_REAL *ce_high = outputs[0]; + TI_REAL *ce_low = outputs[1]; + + if (size <= ti_ce_start(options)) return TI_OKAY; + if (period < 1) return TI_INVALID_OPTION; + + int i; + TI_REAL atr = high[0] - low[0]; + + TI_REAL truerange; /* set within CALC_TRUERANGE macro */ + TI_REAL val; + + TI_REAL HP = high[0]; + int HP_idx = 0; + TI_REAL LP = low[0]; + int LP_idx = 0; + + for (i = 1; i < period; ++i) { + CALC_TRUERANGE(); + atr += truerange; + + if (HP <= (val = high[i])) { + HP = val; + HP_idx = i; + } + if (LP >= (val = low[i])) { + LP = val; + LP_idx = i; + } + } + atr /= (double)period; + + /* performance reasons */ + const TI_REAL smth = (period - 1.) / period; + const TI_REAL per = 1. / period; + + *ce_high++ = HP - coef * atr; + *ce_low++ = LP + coef * atr; + + for (i = period; i < size; ++i) { + CALC_TRUERANGE(); + atr = atr * smth + truerange * per; + + if (HP <= (val = high[i])) { + HP = val; + HP_idx = i; + } else if (HP_idx == i - period) { + HP = high[i-period+1]; + HP_idx = i-period+1; + int j; + for (j = i-period+2; j <= i; ++j) { + if (HP <= (val = high[j])) { + HP = val; + HP_idx = j; + } + } + } + if (LP >= (val = low[i])) { + LP = val; + LP_idx = i; + } else if (LP_idx == i - period) { + LP = low[i-period+1]; + LP_idx = i-period+1; + int j; + for (j = i-period+2; j <= i; ++j) { + if (LP >= (val = low[j])) { + LP = val; + LP_idx = j; + } + } + } + + *ce_high++ = HP - coef * atr; + *ce_low++ = LP + coef * atr; + } + + return TI_OKAY; +} + + +/*********** Reference implementation ************/ + +int ti_ce_ref(int size, TI_REAL const *const *inputs, TI_REAL const *options, TI_REAL *const *outputs) { + TI_REAL const *high = inputs[0]; + TI_REAL const *low = inputs[1]; + TI_REAL const *close = inputs[2]; + const int period = options[0]; + const TI_REAL coef = options[1]; + TI_REAL *ce_high = outputs[0]; + TI_REAL *ce_low = outputs[1]; + + if (size <= ti_ce_start(options)) { return TI_OKAY; } + if (period < 1) { return TI_INVALID_OPTION; } + + #define MAX(a, b) (a > b ? a : b) + #define MAX3(a, b, c) (a > b && a > c ? a : b > a && b > c ? b : c) + + TI_REAL *atr = malloc((size - ti_atr_start(options)) * sizeof(TI_REAL)); + ti_atr(size, inputs, options, &atr); + TI_REAL *max = malloc((size - ti_max_start(options)) * sizeof(TI_REAL)); + ti_max(size, &high, options, &max); + TI_REAL *min = malloc((size - ti_min_start(options)) * sizeof(TI_REAL)); + ti_min(size, &low, options, &min); + + + for (int i = 0; i < size - period + 1; ++i) { + TI_REAL HP = max[i]; + *ce_high++ = HP - coef * atr[i]; + + TI_REAL LP = min[i]; + *ce_low++ = LP + coef * atr[i]; + } + + free(atr); + free(max); + free(min); + + return TI_OKAY; +} + + +/*********** Streaming implementation ************/ + +struct ti_ringbuffer_minmax { + int size; + int end_idx; + int min_idx; + int max_idx; + TI_REAL min; + TI_REAL max; + TI_REAL buffer[][2]; // [i] = min, max +}; + +typedef struct ti_ringbuffer_minmax ti_ringbuffer_minmax; + +struct ti_stream { + int index; + int progress; + + int period; + TI_REAL coef; + + TI_REAL atr; + + TI_REAL prev_close; + + ti_ringbuffer_minmax LP_HP; +}; + +#define PUSH(RINGBUFFER, MIN, MAX) do { \ + int end_idx = ((RINGBUFFER).end_idx + 1) % (RINGBUFFER).size; \ + (RINGBUFFER).end_idx = end_idx; \ + \ + (RINGBUFFER).buffer[end_idx][1] = (MAX); \ + (RINGBUFFER).buffer[end_idx][0] = (MIN); \ + TI_REAL val; \ + if ((RINGBUFFER).max_idx == end_idx) { \ + (RINGBUFFER).max = (RINGBUFFER).buffer[0][1]; \ + (RINGBUFFER).max_idx = 0; \ + int j; \ + for (j = 1; j < (RINGBUFFER).size; ++j) { \ + if ((RINGBUFFER).max <= (val = (RINGBUFFER).buffer[j][1])) { \ + (RINGBUFFER).max = val; \ + (RINGBUFFER).max_idx = j; \ + } \ + } \ + } else if ((RINGBUFFER).max <= (MAX)) { \ + (RINGBUFFER).max = (MAX); \ + (RINGBUFFER).max_idx = end_idx; \ + } \ + if ((RINGBUFFER).min_idx == end_idx) { \ + (RINGBUFFER).min = (RINGBUFFER).buffer[0][0]; \ + (RINGBUFFER).min_idx = 0; \ + int j; \ + for (j = 1; j < (RINGBUFFER).size; ++j) { \ + if ((RINGBUFFER).min >= (val = (RINGBUFFER).buffer[j][0])) { \ + (RINGBUFFER).min = val; \ + (RINGBUFFER).min_idx = j; \ + } \ + } \ + } else if ((RINGBUFFER).min >= (MIN)) { \ + (RINGBUFFER).min = (MIN); \ + (RINGBUFFER).min_idx = end_idx; \ + } \ +} while (0) \ + +#define GET(RINGBUFFER, MIN, MAX) do { \ + (MIN) = (RINGBUFFER).buffer[(RINGBUFFER).min_idx][0]; \ + (MAX) = (RINGBUFFER).buffer[(RINGBUFFER).max_idx][1]; \ +} while (0) \ + +#define INIT(RINGBUFFER, PERIOD) do { \ + (RINGBUFFER).size = (PERIOD); \ + (RINGBUFFER).end_idx = -1; \ + (RINGBUFFER).min_idx = 0; \ + (RINGBUFFER).max_idx = 0; \ + for (int i = 0; i < (PERIOD); ++i) { \ + (RINGBUFFER).buffer[i][0] = 0; \ + (RINGBUFFER).buffer[i][1] = 0; \ + } \ +} while (0) \ + +int ti_ce_stream_new(TI_REAL const *options, ti_stream **stream) { + const int period = options[0]; + const TI_REAL coef = options[1]; + *stream = malloc(sizeof(ti_stream) + sizeof(TI_REAL[period][2])); + if (!*stream) { + return TI_OUT_OF_MEMORY; + } + + (*stream)->index = TI_INDICATOR_CE_INDEX; + (*stream)->progress = -ti_atr_start(options); + + (*stream)->period = period; + (*stream)->coef = coef; + + INIT((*stream)->LP_HP, period); + + return TI_OKAY; +} + +void ti_ce_stream_free(ti_stream *stream) { + free(stream); +} + +#undef CALC_TRUERANGE +#define CALC_TRUERANGE() do {\ + const TI_REAL l = low[i];\ + const TI_REAL h = high[i];\ + const TI_REAL c = prev_close;\ + const TI_REAL ych = fabs(h - c);\ + const TI_REAL ycl = fabs(l - c);\ + TI_REAL v = h - l;\ + if (ych > v) v = ych;\ + if (ycl > v) v = ycl;\ + truerange = v;\ +} while (0) + + +int ti_ce_stream_run(ti_stream *stream, int size, TI_REAL const *const *inputs, TI_REAL *const *outputs) { + int progress = stream->progress; + + TI_REAL const *high = inputs[0]; + TI_REAL const *low = inputs[1]; + TI_REAL const *close = inputs[2]; + const int period = stream->period; + const TI_REAL coef = stream->coef; + TI_REAL *ce_high = outputs[0]; + TI_REAL *ce_low = outputs[1]; + + TI_REAL atr; + + TI_REAL prev_close; + + ti_ringbuffer_minmax *LP_HP = &stream->LP_HP; + + const TI_REAL smth = (period - 1.) / period; + const TI_REAL per = 1. / period; + TI_REAL LP; + TI_REAL HP; + + int i = 0; + if (progress == -period+1) { // initialize + atr = high[0] - low[0]; + + PUSH(*LP_HP, low[0], high[0]); + + prev_close = close[0]; + + ++i, ++progress; + } else { // otherwise, load + atr = stream->atr; + + prev_close = stream->prev_close; + } + for (i; progress < 1 && i < size; ++i, ++progress) { // warm up + TI_REAL truerange; CALC_TRUERANGE(); + atr += truerange; + + PUSH(*LP_HP, low[i], high[i]); + + prev_close = close[i]; + } + if (i > 0 && progress == 1) { // just warmed + atr /= (double)period; + GET(*LP_HP, LP, HP); + *ce_high++ = HP - coef * atr; + *ce_low++ = LP + coef * atr; + } + for (i; i < size; ++i, ++progress) { // continue in normal mode + TI_REAL truerange; CALC_TRUERANGE(); + atr = atr * smth + truerange * per; + + PUSH(*LP_HP, low[i], high[i]); + GET(*LP_HP, LP, HP); + + *ce_high++ = HP - coef * atr; + *ce_low++ = LP + coef * atr; + + prev_close = close[i]; + } + + stream->progress = progress; + stream->atr = atr; + stream->prev_close = prev_close; + + + return TI_OKAY; +} \ No newline at end of file diff --git a/indicators/max.c b/indicators/max.c index b5580d4..c9a7485 100644 --- a/indicators/max.c +++ b/indicators/max.c @@ -67,3 +67,25 @@ int ti_max(int size, TI_REAL const *const *inputs, TI_REAL const *options, TI_RE assert(output - outputs[0] == size - ti_max_start(options)); return TI_OKAY; } + + +int ti_max_ref(int size, TI_REAL const *const *inputs, TI_REAL const *options, TI_REAL *const *outputs) { + const TI_REAL *input = inputs[0]; + const int period = (int)options[0]; + TI_REAL *output = outputs[0]; + + if (period < 1) return TI_INVALID_OPTION; + if (size <= ti_max_start(options)) return TI_OKAY; + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + for (int i = period-1; i < size; ++i) { + TI_REAL max = input[i-period+1]; + for (int j = i-period+2; j <= i; ++j) { + max = MAX(max, input[j]); + } + *output++ = max; + } + + return TI_OKAY; +} \ No newline at end of file diff --git a/indicators/min.c b/indicators/min.c index ddf64fd..e1d8f87 100644 --- a/indicators/min.c +++ b/indicators/min.c @@ -66,3 +66,25 @@ int ti_min(int size, TI_REAL const *const *inputs, TI_REAL const *options, TI_RE assert(output - outputs[0] == size - ti_min_start(options)); return TI_OKAY; } + +int ti_min_ref(int size, TI_REAL const *const *inputs, TI_REAL const *options, TI_REAL *const *outputs) { + const TI_REAL *input = inputs[0]; + const int period = (int)options[0]; + TI_REAL *output = outputs[0]; + + if (period < 1) return TI_INVALID_OPTION; + if (size <= ti_min_start(options)) return TI_OKAY; + + #define MIN(a, b) ((a) < (b) ? (a) : (b)) + + for (int i = period-1; i < size; ++i) { + TI_REAL min = input[i-period+1]; + for (int j = i-period+2; j <= i; ++j) { + min = MIN(min, input[j]); + } + *output++ = min; + } + + assert(output - outputs[0] == size - ti_min_start(options)); + return TI_OKAY; +} \ No newline at end of file diff --git a/tests/extra.txt b/tests/extra.txt index df6f365..5aec5b0 100644 --- a/tests/extra.txt +++ b/tests/extra.txt @@ -56,6 +56,15 @@ bop {81.59,81.06,82.87,83,83.61,83.15,82.84,83.99,84.55,84.36,85.53,86.54} {-0.3023,-0.112,0.7674,0.1385,0.6538,-0.3291,0.1548,0.645,0.5072,0.1236,0.8021,0.916} +ce 22 3 +# Data: AAPL NASDAQ, 3m +# Baseline: ref implementation +{167.45,162.11,158.16,151.55,157.23,156.77,158.52,159.36,158.85,145.72,148.5499,148.83,151.82,154.53,153.97,153.7,151.27,153.39,155.88,157.66,157.88,156.73,155.14,154.48,158.13,156.33,158.13,166.15,169.0,168.98,171.655,175.08,175.57,173.94,170.66,171.21,171.0,172.48,171.2615,171.7,171.44,173.32,172.37,173.0,175.87,175.3,175.0,174.91,175.15,177.75,176.0,175.49,174.44,173.07,179.12,182.67,183.3,184.1,187.33,188.39,188.99}; +{159.09,155.3,149.63,146.59,146.72,150.07,154.55,156.48,154.23,142.0,143.8,145.9,148.52,149.63,150.86,151.51,149.22,150.05,153.0,153.26,155.9806,152.62,151.7,151.74,154.32,153.66,154.11,160.23,164.56,165.93,167.28,172.3501,172.8531,170.34,168.42,169.25,169.7,169.92,169.38,169.75,169.49,170.99,170.3,171.38,173.95,173.1732,172.73,172.92,172.89,173.97,174.54,173.94,172.02,169.5,175.35,179.37,180.92,182.56,183.74,185.79,185.92}; +{160.89,156.83,150.73,146.83,157.17,156.15,156.23,157.74,157.92,142.19,148.26,147.93,150.75,153.31,153.8,152.29,150.0,153.07,154.94,155.86,156.82,153.3,153.92,152.7,157.76,156.3,154.68,165.25,166.44,166.52,171.25,174.18,174.24,170.94,170.41,169.43,170.89,170.18,170.8,170.42,170.93,172.03,171.06,172.97,174.23,174.33,174.87,173.15,174.97,175.85,175.53,174.52,172.5,172.91,178.9,180.91,181.71,183.73,186.12,188.02,186.53}; +{151.448,146.366,143.958,143.918,144.060,144.208,150.122,153.095,153.402,156.066,159.678,160.497,160.650,160.985,161.381,161.812,162.088,162.444,162.775,163.091,163.332,163.606,163.885,164.321,164.556,164.761,164.994,165.180,167.031,167.319,167.576,167.698,167.668,168.649,172.161,172.943,173.888,177.091,178.262,178.904,153.926} +{158.002,157.744,157.402,157.442,157.300,157.152,158.028,157.905,157.598,157.589,159.202,160.973,163.440,163.805,163.409,162.978,162.702,163.176,164.495,164.179,163.938,163.664,163.385,163.289,164.974,164.769,164.986,170.920,175.279,176.361,177.454,178.472,178.502,178.891,178.929,179.607,179.592,179.619,179.508,179.576,35.064} + ceil {0,.5,1} {0,1,1}