Skip to content

Commit

Permalink
more indicators
Browse files Browse the repository at this point in the history
- true range
- trend intensity index
- trade volume index
- triple exponential average
- typical price index
- supertrend
- linreg smooth
  • Loading branch information
chungg committed May 15, 2024
1 parent 2842aef commit 61638af
Show file tree
Hide file tree
Showing 5 changed files with 382 additions and 7 deletions.
136 changes: 130 additions & 6 deletions src/indicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,12 +451,7 @@ pub fn ultimate(
/// pretty good oscillator
/// https://library.tradingtechnologies.com/trade/chrt-ti-pretty-good-oscillator.html
pub fn pgo(high: &[f64], low: &[f64], close: &[f64], window: u8) -> Vec<f64> {
let atr = smooth::ewma(
&izip!(&high[1..], &low[1..], &close[..close.len() - 1])
.map(|(h, l, prevc)| (h - l).max(f64::abs(h - prevc)).max(f64::abs(l - prevc)))
.collect::<Vec<f64>>(),
window,
);
let atr = smooth::ewma(&_true_range(high, low, close).collect::<Vec<f64>>(), window);
let sma_close = smooth::sma(close, window);
izip!(
&close[close.len() - atr.len()..],
Expand Down Expand Up @@ -539,3 +534,132 @@ pub fn ulcer(data: &[f64], window: u8) -> Vec<f64> {
.map(|x| x.sqrt())
.collect::<Vec<f64>>()
}

fn _true_range<'a>(
high: &'a [f64],
low: &'a [f64],
close: &'a [f64],
) -> impl Iterator<Item = f64> + 'a {
izip!(&high[1..], &low[1..], &close[..close.len() - 1])
.map(|(h, l, prevc)| (h - l).max(f64::abs(h - prevc)).max(f64::abs(l - prevc)))
}

/// true range
/// https://www.investopedia.com/terms/a/atr.asp
pub fn tr(high: &[f64], low: &[f64], close: &[f64]) -> Vec<f64> {
_true_range(high, low, close).collect::<Vec<f64>>()
}

/// typical price
/// https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/typical-price
pub fn hlc3(high: &[f64], low: &[f64], close: &[f64], window: u8) -> Vec<f64> {
smooth::sma(
&izip!(high, low, close)
.map(|(h, l, c)| (h + l + c) / 3.0)
.collect::<Vec<f64>>(),
window,
)
}

/// Triple Exponential Average
/// https://www.investopedia.com/terms/t/trix.asp
pub fn trix(close: &[f64], window: u8) -> Vec<f64> {
let ema3 = smooth::ewma(&smooth::ewma(&smooth::ewma(close, window), window), window);
ema3[..ema3.len() - 1]
.iter()
.zip(&ema3[1..])
.map(|(prev, curr)| 100.0 * (curr - prev) / prev)
.collect::<Vec<f64>>()
}

/// trend intensity index
/// https://www.marketvolume.com/technicalanalysis/trendintensityindex.asp
pub fn tii(data: &[f64], window: u8) -> Vec<f64> {
smooth::sma(data, window)
.iter()
.zip(&data[(window - 1) as usize..])
.map(|(avg, actual)| {
let dev: f64 = actual - avg;
let pos_dev = if dev > 0.0 { dev } else { 0.0 };
let neg_dev = if dev < 0.0 { dev.abs() } else { 0.0 };
(pos_dev, neg_dev)
})
.collect::<Vec<(f64, f64)>>()
.windows(u8::div_ceil(window, 2).into())
.map(|w| {
let mut sd_pos = 0.0;
let mut sd_neg = 0.0;
for (pos_dev, neg_dev) in w {
sd_pos += pos_dev;
sd_neg += neg_dev;
}
100.0 * sd_pos / (sd_pos + sd_neg)
})
.collect::<Vec<f64>>()
}

/// trade volume index
/// https://www.investopedia.com/terms/t/tradevolumeindex.asp
pub fn tvi(close: &[f64], volume: &[f64], min_tick: f64) -> Vec<f64> {
izip!(&close[..close.len() - 1], &close[1..], &volume[1..],)
.scan((1, 0.0), |state, (prev, curr, vol)| {
let direction = if curr - prev > min_tick {
1
} else if prev - curr > min_tick {
-1
} else {
state.0
};
let tvi = state.1 + direction as f64 * vol;
*state = (direction, tvi);
Some(tvi)
})
.collect::<Vec<f64>>()
}

/// supertrend
/// https://www.tradingview.com/support/solutions/43000634738-supertrend/
/// https://www.investopedia.com/supertrend-indicator-7976167
pub fn supertrend(
high: &[f64],
low: &[f64],
close: &[f64],
window: u8,
multiplier: f64,
) -> Vec<f64> {
let atr = smooth::wilder(&_true_range(high, low, close).collect::<Vec<f64>>(), window);
izip!(
&high[window.into()..],
&low[window.into()..],
&close[window.into()..],
&atr
)
.scan(
(f64::NAN, f64::NAN, f64::MIN_POSITIVE, 1),
|state, (h, l, c, tr)| {
let (prevlower, prevupper, prevc, prevdir) = state;
let mut lower = (h + l) / 2.0 - multiplier * tr;
let mut upper = (h + l) / 2.0 + multiplier * tr;
if prevc > prevlower && *prevlower > lower {
lower = *prevlower;
}
if prevc < prevupper && *prevupper < upper {
upper = *prevupper;
}
let dir = if c > prevupper {
1
} else if c < prevlower {
-1
} else {
*prevdir
};
*state = (lower, upper, *c, dir);
if dir > 0 {
Some(lower)
} else {
Some(upper)
}
},
)
.collect::<Vec<f64>>()
}
8 changes: 7 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@ fn main() {
let data = fs::read_to_string("./tests/rddt.input").expect("Unable to read file");
let stats: SecStats = serde_json::from_str(&data).expect("JSON does not have correct format.");

dbg!(indicator::ulcer(&stats.close, 8));
dbg!(indicator::supertrend(
&stats.high,
&stats.low,
&stats.close,
16,
3.0
));
}
23 changes: 23 additions & 0 deletions src/smooth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,26 @@ pub fn vma(data: &[f64], window: u8) -> Vec<f64> {
.skip((u8::max(9, window) - 9).into())
.collect::<Vec<f64>>()
}

/// Linear Regression Forecast
/// aka Time series forecast
/// https://quantstrategy.io/blog/what-is-tsf-understanding-time-series-forecast-indicator/
pub fn lrf(data: &[f64], window: u16) -> Vec<f64> {
let x_sum = (window * (window + 1)) as f64 / 2.0;
let x2_sum: f64 = x_sum * (2 * window + 1) as f64 / 3.0;
let divisor = window as f64 * x2_sum - x_sum.powi(2);

data.windows(window.into())
.map(|w| {
let mut y_sum = 0.0;
let mut xy_sum = 0.0;
for (count, val) in w.iter().enumerate() {
y_sum += val;
xy_sum += (count + 1) as f64 * val;
}
let m = (window as f64 * xy_sum - x_sum * y_sum) / divisor;
let b = (y_sum * x2_sum - x_sum * xy_sum) / divisor;
m * window as f64 + b
})
.collect::<Vec<f64>>()
}
192 changes: 192 additions & 0 deletions tests/indicator_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -874,3 +874,195 @@ fn test_ulcer() {
result
);
}

#[test]
fn test_tr() {
let stats = common::test_data();
let result = indicator::tr(&stats.high, &stats.low, &stats.close);
assert_eq!(
vec![
15.939998626708984,
15.10000228881836,
9.490001678466797,
8.650001525878906,
4.924999237060547,
7.349998474121094,
4.69000244140625,
3.3300018310546875,
3.6100006103515625,
4.1399993896484375,
2.5900001525878906,
3.279998779296875,
4.340000152587891,
2.3699989318847656,
2.5900001525878906,
2.8199996948242188,
2.4399986267089844,
4.780002593994141,
3.660003662109375,
2.0600013732910156,
2.2380027770996094,
1.5200004577636719,
2.3000030517578125,
3.7490005493164063,
3.450000762939453,
2.604999542236328,
3.2600021362304688,
3.918998718261726,
2.4599990844726563,
3.229999542236328,
2.9300003051757813,
5.759998321533203,
3.1500015258789063,
],
result
);
}

#[test]
fn test_hlc3() {
let stats = common::test_data();
let result = indicator::hlc3(&stats.high, &stats.low, &stats.close, 16);
assert_eq!(
vec![
48.82131250699361,
48.41006247202555,
47.38204161326091,
45.67329160372416,
44.58475001653036,
43.98891671498617,
43.760937531789146,
43.422812620798744,
43.03031253814698,
42.92550015449524,
42.935500065485634,
42.83081253369649,
42.84727080663045,
43.15249999364217,
43.33354179064433,
43.67937509218853,
44.2075002193451,
44.90875029563905,
45.53729192415874,
],
result
);
}

#[test]
fn test_trix() {
let stats = common::test_data();
let result = indicator::trix(&stats.close, 7);
assert_eq!(
vec![
-1.7609812348121436,
-1.58700358125189,
-1.3595873824994853,
-1.1099628496419054,
-0.8955729429209658,
-0.6091528694171965,
-0.2949587326281726,
-0.07920030554104754,
0.1135712345224751,
0.32952700679764474,
0.4769166955410566,
0.6219613185170387,
0.7718438325710452,
0.9560958810856369,
1.0335513961870586,
],
result
);
}

#[test]
fn test_tii_even() {
let stats = common::test_data();
let result = indicator::tii(&stats.close, 16);
assert_eq!(
vec![
0.0,
0.0,
12.365592243625398,
36.838871014658466,
53.81832516603384,
77.25510120423154,
91.83894521268712,
97.2706485470137,
98.02691957272636,
100.0,
100.0,
100.0,
],
result
);
}

#[test]
fn test_tii_odd() {
let stats = common::test_data();
let result = indicator::tii(&stats.close, 15);
assert_eq!(
vec![
0.0,
0.6686354433953655,
0.9316341618307428,
17.111743788068527,
44.82345282880815,
61.21209452789354,
83.24057181663895,
96.05752982533481,
98.58952713255982,
98.83515680783523,
100.0,
100.0,
100.0,
],
result
);
}

#[test]
fn test_tvi() {
let stats = common::test_data();
let result = indicator::tvi(&stats.close, &stats.volume, 0.5);
assert_eq!(
vec![
24398800.0, 59729800.0, 40971500.0, 28363400.0, 15375500.0, 24818400.0, 19995500.0,
15377100.0, 18304100.0, 15642600.0, 13365300.0, 9015200.0, 13278200.0, 11264800.0,
7816300.0, 9515300.0, 7361500.0, 9645600.0, 7117500.0, 8603900.0, 10560400.0,
11944400.0, 10458600.0, 13222800.0, 15901100.0, 14148200.0, 15746300.0, 18111300.0,
16923000.0, 19039700.0, 25281400.0, 38772900.0, 36090498.0,
],
result
);
}

#[test]
fn test_supertrend() {
let stats = common::test_data();
let result = indicator::supertrend(&stats.high, &stats.low, &stats.close, 16, 3.0);
assert_eq!(
vec![
22.87718629837036,
22.87718629837036,
22.87718629837036,
25.36115028045606,
25.5548271386142,
27.535275836318306,
28.482134516844127,
28.482134516844127,
30.372852510605856,
33.54470377638434,
33.54470377638434,
33.54470377638434,
34.55477737989572,
34.70432339242238,
35.73655158775987,
36.52801923545023,
39.78407957956028,
39.78407957956028,
],
result
);
}
Loading

0 comments on commit 61638af

Please sign in to comment.