diff --git a/risk/_actor.py b/risk/_actor.py index f3426592..ef4054bf 100644 --- a/risk/_actor.py +++ b/risk/_actor.py @@ -9,7 +9,7 @@ PositionClosed, PositionOpened, ) -from core.events.risk import RiskThresholdBreached, RiskType +from core.events.risk import RiskAdjustRequested, RiskThresholdBreached, RiskType from core.events.signal import ( ExitLongSignalReceived, ExitShortSignalReceived, @@ -69,8 +69,8 @@ async def on_receive(self, event: RiskEvent): PositionOpened: self._open_position, PositionClosed: self._close_position, PositionAdjusted: self._adjust_position, - GoLongSignalReceived: self._handle_reverse, - GoShortSignalReceived: self._handle_reverse, + GoLongSignalReceived: [self._handle_reverse, self._handle_scale_in], + GoShortSignalReceived: [self._handle_reverse, self._handle_scale_in], ExitLongSignalReceived: self._handle_signal_exit, ExitShortSignalReceived: self._handle_signal_exit, } @@ -78,7 +78,11 @@ async def on_receive(self, event: RiskEvent): handler = handlers.get(type(event)) if handler: - await handler(event) + if isinstance(handler, list): + for h in handler: + await h(event) + else: + await handler(event) async def _open_position(self, event: PositionOpened): async with self.lock: @@ -159,6 +163,27 @@ async def _handle_reverse( ): await self._process_reverse_exit(short_position, event.entry_price) + async def _handle_scale_in( + self, event: Union[GoLongSignalReceived, GoShortSignalReceived] + ): + async with self.lock: + long_position, short_position = self._position + + if ( + isinstance(event, GoLongSignalReceived) + and long_position + and long_position.adj_count < self.config["max_scale_in"] + and long_position.entry_price < event.entry_price + ): + await self.tell(RiskAdjustRequested(long_position, event.entry_price)) + if ( + isinstance(event, GoShortSignalReceived) + and short_position + and short_position.adj_count < self.config["max_scale_in"] + and short_position.entry_price > event.entry_price + ): + await self.tell(RiskAdjustRequested(short_position, event.entry_price)) + async def _handle_signal_exit( self, event: Union[ExitLongSignalReceived, ExitShortSignalReceived] ): diff --git a/ta_lib/core/src/macros.rs b/ta_lib/core/src/macros.rs index bd442c0a..d3584cb9 100644 --- a/ta_lib/core/src/macros.rs +++ b/ta_lib/core/src/macros.rs @@ -48,12 +48,12 @@ mod tests { #[test] fn test_nz() { - let source = Series::from([f32::NAN, 5.0, 4.0, 3.0, 5.0]); + let source = Series::from([3.0, 5.0, 4.0, 3.0, 5.0]); let fill = Series::from([1.0, 0.5, 5.0, 2.0, 8.0]); - let expected = Series::from([1.0, 5.0, 4.0, 3.0, 5.0]); + let expected = Series::from([1.0, 3.0, 5.0, 4.0, 3.0]); - let result = nz!(source, fill); + let result = nz!(source.shift(1), fill); assert_eq!(result, expected); } diff --git a/ta_lib/core/src/math.rs b/ta_lib/core/src/math.rs index 9d8d4ed5..d7c7dc20 100644 --- a/ta_lib/core/src/math.rs +++ b/ta_lib/core/src/math.rs @@ -1,9 +1,8 @@ use crate::series::Series; use crate::smoothing::Smooth; +use crate::ZERO; use std::ops::Neg; -const ZERO: f32 = 0.; - impl Series { pub fn abs(&self) -> Self { self.fmap(|val| val.map(|v| v.abs())) @@ -57,7 +56,13 @@ impl Series { impl Series { pub fn sum(&self, period: usize) -> Self { self.window(period) - .map(|w| w.iter().flatten().sum::()) + .map(|w| { + if w.iter().all(|&x| x.is_none()) { + None + } else { + Some(w.iter().flatten().sum::()) + } + }) .collect() } @@ -72,14 +77,21 @@ impl Series { pub fn mad(&self, period: usize) -> Self { self.window(period) .map(|w| { - let len = w.len() as f32; - let mean = w.iter().flatten().sum::() / len; - - w.iter() - .flatten() - .map(|value| (value - mean).abs()) - .sum::() - / len + if w.iter().all(|&x| x.is_none()) { + None + } else { + let len = w.len() as f32; + let mean = w.iter().flatten().sum::() / len; + + let mad = w + .iter() + .flatten() + .map(|value| (value - mean).abs()) + .sum::() + / len; + + Some(mad) + } }) .collect() } @@ -195,7 +207,7 @@ mod tests { #[test] fn test_sum() { let source = Series::from([f32::NAN, 2.0, 3.0, 4.0, 5.0]); - let expected = Series::from([0.0, 2.0, 5.0, 9.0, 12.0]); + let expected = Series::from([f32::NAN, 2.0, 5.0, 9.0, 12.0]); let n = 3; let result = source.sum(n); diff --git a/ta_lib/core/src/series.rs b/ta_lib/core/src/series.rs index be685539..0d78d39f 100644 --- a/ta_lib/core/src/series.rs +++ b/ta_lib/core/src/series.rs @@ -243,8 +243,8 @@ mod tests { #[test] fn test_lowest() { - let source = Series::from([1.0, 2.0, 3.0, 4.0, 5.0]); - let expected = Series::from([1.0, 1.0, 1.0, 2.0, 3.0]); + let source = Series::from([f32::NAN, 2.0, 3.0, 1.0, 5.0]); + let expected = Series::from([f32::NAN, 2.0, 2.0, 1.0, 1.0]); let period = 3; let result = source.lowest(period); diff --git a/ta_lib/core/src/smoothing.rs b/ta_lib/core/src/smoothing.rs index e0b4937f..8c2a992e 100644 --- a/ta_lib/core/src/smoothing.rs +++ b/ta_lib/core/src/smoothing.rs @@ -1,5 +1,7 @@ -use crate::iff; use crate::series::Series; +use crate::traits::Comparator; +use crate::ZERO; +use crate::{iff, nz}; #[derive(Copy, Clone)] pub enum Smooth { @@ -19,9 +21,7 @@ impl Series { let mut sum = Series::zero(len); for _ in 0..len { - let prev = sum.shift(1); - - sum = iff!(prev.na(), seed, alpha * self + (1. - alpha) * prev) + sum = alpha * self + (1. - alpha) * nz!(sum.shift(1), seed) } sum @@ -40,7 +40,13 @@ impl Series { fn ma(&self, period: usize) -> Self { self.window(period) - .map(|w| w.iter().flatten().sum::() / w.len() as f32) + .map(|w| { + if w.iter().all(|&x| x.is_none()) { + None + } else { + Some(w.iter().flatten().sum::() / w.len() as f32) + } + }) .collect() } @@ -99,30 +105,13 @@ impl Series { fn kama(&self, period: usize) -> Series { let len = self.len(); - let change = self.change(period).abs(); + let mom = self.change(period).abs(); let volatility = self.change(1).abs().sum(period); + let er = iff!(volatility.seq(&ZERO), Series::zero(len), mom / volatility); - let er = change / volatility; - - let alpha = iff!( - er.na(), - Series::fill(2. / (period as f32 + 1.), len), - (er * 0.666_666_7).sqrt() - ); - - let mut kama = Series::empty(len); - - for _ in 0..len { - let prev_kama = kama.shift(1); - - kama = iff!( - prev_kama.na(), - self, - &prev_kama + &alpha * (self - &prev_kama) - ) - } + let alpha = (er * 0.666_666_7).pow(2); - kama + self.ew(&alpha, self) } fn zlema(&self, period: usize) -> Series { @@ -151,8 +140,8 @@ mod tests { #[test] fn test_ma() { - let source = Series::from([1.0, 2.0, 3.0, 4.0, 5.0]); - let expected = Series::from([1.0, 1.5, 2.0, 3.0, 4.0]); + let source = Series::from([f32::NAN, 2.0, 3.0, 4.0, 5.0]); + let expected = Series::from([f32::NAN, 1.0, 1.6666666, 3.0, 4.0]); let result = source.ma(3); @@ -199,6 +188,16 @@ mod tests { assert_eq!(result, expected); } + #[test] + fn test_kama() { + let source = Series::from([1.0, 2.0, 3.0, 4.0, 5.0]); + let expected = Series::from([f32::NAN, f32::NAN, f32::NAN, 4.0, 4.4444447]); + + let result = source.kama(3); + + assert_eq!(result, expected); + } + #[test] fn test_linreg() { let source = Series::from([ diff --git a/ta_lib/indicators/momentum/src/kst.rs b/ta_lib/indicators/momentum/src/kst.rs index c32bde1c..a865498c 100644 --- a/ta_lib/indicators/momentum/src/kst.rs +++ b/ta_lib/indicators/momentum/src/kst.rs @@ -39,17 +39,7 @@ mod tests { let period_four = 5; let expected = vec![ - 0.0, - 0.0, - -0.08674153, - -0.07785419, - 0.8893757, - 3.2460651, - 5.4379296, - 7.731903, - 8.935576, - 9.378514, - 9.048229, + 0.0, 0.0, 0.0, 0.0, 0.0, 3.2460651, 5.4379296, 7.731903, 8.935576, 9.378514, 9.048229, 8.414183, ]; diff --git a/ta_lib/indicators/trend/src/frama.rs b/ta_lib/indicators/trend/src/frama.rs index ffd84f2a..723df8d0 100644 --- a/ta_lib/indicators/trend/src/frama.rs +++ b/ta_lib/indicators/trend/src/frama.rs @@ -1,32 +1,24 @@ use core::prelude::*; pub fn frama( + source: &Series, high: &Series, low: &Series, - close: &Series, period: usize, ) -> Series { - let hh1 = high.highest(2 * period).shift(period); - let ll1 = low.lowest(2 * period).shift(period); - let n1 = (&hh1 - &ll1) / period as f32; + let len = (0.5 * period as f32).round() as usize; + let hh = high.highest(len); + let ll = low.lowest(len); - let hh2 = high.highest(period); - let ll2 = low.lowest(period); - let n2 = (&hh2 - &ll2) / period as f32; + let n1 = (&hh - &ll) / len as f32; + let n2 = (hh.shift(len) - ll.shift(len)) / len as f32; + let n3 = (high.highest(period) - low.lowest(period)) / period as f32; - let hh3 = hh1.max(&hh2); - let ll3 = ll1.min(&ll2); - let n3 = (hh3 - ll3) / (2. * period as f32); + let d = ((n1 + n2) / n3).log() / 2.0_f32.ln(); - let d = ((n1 + n2).log() - n3.log()) / 2.0_f32.ln(); + let alpha = (-4.6 * (d - 1.)).exp(); - let alpha = iff!( - d.na(), - Series::fill(2. / (period as f32 + 1.), close.len()), - (-4.6 * (d - 1.)).exp() - ); - - close.ew(&alpha, close) + source.ew(&alpha, source) } #[cfg(test)] @@ -35,12 +27,24 @@ mod tests { #[test] fn test_frama() { - let high = Series::from([18.904, 18.988, 18.992, 18.979, 18.941]); - let low = Series::from([18.825, 18.866, 18.950, 18.912, 18.877]); - let close = Series::from([18.889, 18.966, 18.963, 18.922, 18.940]); - let expected = vec![18.889, 18.9275, 18.94525, 18.939285, 18.939308]; - - let result: Vec = frama(&high, &low, &close, 3).into(); + let high = Series::from([ + 5.0994, 5.0910, 5.1015, 5.1051, 5.1041, 5.1138, 5.1150, 5.1160, 5.1392, 5.1550, 5.1739, + 5.2000, 5.2193, + ]); + let low = Series::from([ + 5.0770, 5.0788, 5.0897, 5.0933, 5.0943, 5.0996, 5.1001, 5.0968, 5.1125, 5.1326, 5.1468, + 5.1675, 5.1907, + ]); + let source = Series::from([ + 5.0788, 5.0897, 5.0958, 5.1023, 5.0998, 5.1046, 5.1014, 5.1141, 5.1355, 5.1477, 5.1675, + 5.2000, 5.2169, + ]); + let expected = vec![ + 0.0, 0.0, 5.0958, 5.09975, 5.0997515, 5.100709, 5.10147, 5.1022835, 5.130958, + 5.2076283, 5.172987, 5.1907825, 5.2242994, + ]; + + let result: Vec = frama(&source, &high, &low, 3).into(); assert_eq!(result, expected); } diff --git a/ta_lib/indicators/trend/src/kama.rs b/ta_lib/indicators/trend/src/kama.rs index 31ccdb8c..01a670d2 100644 --- a/ta_lib/indicators/trend/src/kama.rs +++ b/ta_lib/indicators/trend/src/kama.rs @@ -10,8 +10,14 @@ mod tests { #[test] fn test_kama() { - let source = Series::from([19.099, 19.079, 19.074, 19.139, 19.191]); - let expected = vec![19.099, 19.089, 19.081501, 19.112799, 19.173977]; + let source = Series::from([ + 5.0788, 5.0897, 5.0958, 5.1023, 5.0998, 5.1046, 5.1014, 5.1141, 5.1355, 5.1477, 5.1675, + 5.2000, 5.2169, + ]); + let expected = vec![ + 0.0, 0.0, 0.0, 5.1023, 5.1018033, 5.1023088, 5.102306, 5.104807, 5.1141686, 5.129071, + 5.1461506, 5.1700835, 5.190891, + ]; let result: Vec = kama(&source, 3).into(); diff --git a/ta_lib/indicators/trend/src/md.rs b/ta_lib/indicators/trend/src/md.rs index 04ca78e5..a7aeea93 100644 --- a/ta_lib/indicators/trend/src/md.rs +++ b/ta_lib/indicators/trend/src/md.rs @@ -7,13 +7,8 @@ pub fn md(source: &Series, period: usize) -> Series { let seed = source.smooth(Smooth::EMA, period); for _ in 0..len { - let prev_mg = mg.shift(1); - - mg = iff!( - prev_mg.na(), - seed, - &prev_mg + (source - &prev_mg) / ((source / &prev_mg).pow(4) * period as f32) - ); + let prev_mg = nz!(mg.shift(1), seed); + mg = &prev_mg + (source - &prev_mg) / ((source / &prev_mg).pow(4) * period as f32); } mg diff --git a/ta_lib/indicators/trend/src/smma.rs b/ta_lib/indicators/trend/src/smma.rs index bf3fd285..9708ad82 100644 --- a/ta_lib/indicators/trend/src/smma.rs +++ b/ta_lib/indicators/trend/src/smma.rs @@ -15,7 +15,7 @@ mod tests { 6.8310, 6.8355, 6.8360, 6.8345, 6.8285, 6.8395, ]); let expected = [ - 6.8575, 6.8566666, 6.857111, 6.8580737, 6.8547153, 6.8556433, 6.8584285, 6.857785, + 6.8574996, 6.8566666, 6.857111, 6.8580737, 6.8547153, 6.8556433, 6.8584285, 6.857785, 6.85369, 6.8507934, 6.846029, 6.8410187, 6.839179, 6.8381195, 6.8369126, 6.8341084, 6.835905, ]; diff --git a/ta_lib/indicators/trend/src/vidya.rs b/ta_lib/indicators/trend/src/vidya.rs index 00b86136..91c3f334 100644 --- a/ta_lib/indicators/trend/src/vidya.rs +++ b/ta_lib/indicators/trend/src/vidya.rs @@ -1,7 +1,8 @@ use core::prelude::*; pub fn vidya(source: &Series, fast_period: usize, slow_period: usize) -> Series { - let alpha = 2. / (fast_period as f32 + 1.) * source.std(fast_period) / source.std(slow_period); + let k = source.std(fast_period) / source.std(slow_period); + let alpha = 2. / (fast_period as f32 + 1.) * k.nz(Some(ZERO)); source.ew(&alpha, source) } diff --git a/ta_lib/strategies/indicator/src/ma.rs b/ta_lib/strategies/indicator/src/ma.rs index 273eec37..0ae5b462 100644 --- a/ta_lib/strategies/indicator/src/ma.rs +++ b/ta_lib/strategies/indicator/src/ma.rs @@ -47,7 +47,9 @@ pub fn ma_indicator( MovingAverageType::CAMA => cama(data.close(), data.high(), data.low(), &data.tr(), period), MovingAverageType::DEMA => dema(&data.source(source_type), period), MovingAverageType::EMA => ema(&data.source(source_type), period), - MovingAverageType::FRAMA => frama(data.high(), data.low(), data.close(), period), + MovingAverageType::FRAMA => { + frama(&data.source(source_type), data.high(), data.low(), period) + } MovingAverageType::GMA => gma(&data.source(source_type), period), MovingAverageType::HMA => hma(&data.source(source_type), period), MovingAverageType::HEMA => hema(&data.source(source_type), period), diff --git a/ta_lib/strategies/trend_follow/src/deserialize/source_deserialize.rs b/ta_lib/strategies/trend_follow/src/deserialize/source_deserialize.rs index 4a0479e4..96265612 100644 --- a/ta_lib/strategies/trend_follow/src/deserialize/source_deserialize.rs +++ b/ta_lib/strategies/trend_follow/src/deserialize/source_deserialize.rs @@ -1,5 +1,4 @@ use base::prelude::*; -use core::prelude::*; #[inline] pub fn source_deserialize(source: usize) -> SourceType { diff --git a/ta_lib/strategies/trend_follow/src/mapper/stoploss_mapper.rs b/ta_lib/strategies/trend_follow/src/mapper/stoploss_mapper.rs index 4a57d68a..d8a7612c 100644 --- a/ta_lib/strategies/trend_follow/src/mapper/stoploss_mapper.rs +++ b/ta_lib/strategies/trend_follow/src/mapper/stoploss_mapper.rs @@ -1,5 +1,4 @@ use crate::config::StopLossConfig; -use crate::deserialize::smooth_deserialize; use base::prelude::*; use stop_loss::*;