Skip to content

Commit eab00e3

Browse files
authored
Add unadjusted halflife-based EMA (#570)
Signed-off-by: Adam Glustein <adam.glustein@point72.com>
1 parent 0d91518 commit eab00e3

8 files changed

Lines changed: 270 additions & 95 deletions

File tree

cpp/csp/cppnodes/statsimpl.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ EXPORT_TEMPLATE_CPPNODE( _rank, SINGLE_ARG( _computeTwoArg<int64_t,
299299
EXPORT_TEMPLATE_CPPNODE( _kurt, SINGLE_ARG( _computeTwoArg<bool, Kurtosis> ) );
300300
EXPORT_TEMPLATE_CPPNODE( _ema_compute, _computeEMA<EMA> );
301301
EXPORT_TEMPLATE_CPPNODE( _ema_adjusted, _computeEMA<AdjustedEMA>);
302-
EXPORT_TEMPLATE_CPPNODE( _ema_debias_alpha, _computeEMA<AlphaDebiasEMA> );
302+
EXPORT_TEMPLATE_CPPNODE( _ema_alpha_debias, _computeEMA<AlphaDebiasEMA> );
303303

304304

305305
// The following nodes are written independently from _compute
@@ -533,10 +533,12 @@ DECLARE_CPPNODE ( _quantile )
533533
EXPORT_CPPNODE ( _quantile );
534534

535535
template<typename C>
536-
DECLARE_CPPNODE( _exp_timewise )
536+
DECLARE_CPPNODE( _exp_halflife )
537537
{
538538
TS_INPUT( double, x );
539539
SCALAR_INPUT( TimeDelta, halflife );
540+
SCALAR_INPUT( bool, adjust );
541+
540542
TS_INPUT( Generic, trigger );
541543
TS_INPUT( Generic, sampler );
542544
TS_INPUT( Generic, reset );
@@ -545,11 +547,11 @@ DECLARE_CPPNODE( _exp_timewise )
545547
STATE_VAR( DataValidator<C>, s_computation );
546548
TS_OUTPUT( double );
547549

548-
INIT_CPPNODE( _exp_timewise ) { }
550+
INIT_CPPNODE( _exp_halflife ) { }
549551

550552
START()
551553
{
552-
s_computation = DataValidator<C>( min_data_points, true, halflife, now() );
554+
s_computation = DataValidator<C>( min_data_points, true, halflife, now(), adjust );
553555
}
554556

555557
INVOKE()
@@ -571,8 +573,9 @@ DECLARE_CPPNODE( _exp_timewise )
571573
}
572574
};
573575

574-
EXPORT_TEMPLATE_CPPNODE( _ema_timewise, _exp_timewise<HalflifeEMA> );
575-
EXPORT_TEMPLATE_CPPNODE( _ema_debias_halflife, _exp_timewise<HalflifeDebiasEMA> );
576+
EXPORT_TEMPLATE_CPPNODE( _ema_halflife, _exp_halflife<HalflifeEMA> );
577+
EXPORT_TEMPLATE_CPPNODE( _ema_halflife_adjusted, _exp_halflife<AdjustedHalflifeEMA> );
578+
EXPORT_TEMPLATE_CPPNODE( _ema_halflife_debias, _exp_halflife<HalflifeDebiasEMA> );
576579

577580
DECLARE_CPPNODE( _arg_min_max )
578581
{

cpp/csp/cppnodes/statsimpl.h

Lines changed: 82 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,11 +1628,6 @@ class AlphaDebiasEMA
16281628
m_adjust = adjust;
16291629
reset();
16301630
}
1631-
1632-
AlphaDebiasEMA( AlphaDebiasEMA && rhs ) = default;
1633-
1634-
AlphaDebiasEMA & operator=( AlphaDebiasEMA && rhs ) = default;
1635-
16361631

16371632
void add( double x )
16381633
{
@@ -1724,31 +1719,64 @@ class AlphaDebiasEMA
17241719
};
17251720

17261721
class HalflifeEMA
1722+
{
1723+
public:
1724+
HalflifeEMA() = default;
1725+
1726+
HalflifeEMA( TimeDelta halflife, DateTime start, bool )
1727+
{
1728+
m_decay_factor = log( 0.5 ) / halflife.asNanoseconds();
1729+
reset();
1730+
}
1731+
1732+
void add( double x, DateTime now )
1733+
{
1734+
if( unlikely( m_last_tick.isNone() ) )
1735+
m_ema = x;
1736+
else
1737+
{
1738+
double decay = 1 - exp( m_decay_factor * ( now - m_last_tick ).asNanoseconds() );
1739+
m_ema += decay * ( x - m_ema );
1740+
}
1741+
m_last_tick = now;
1742+
}
1743+
1744+
void reset()
1745+
{
1746+
m_ema = std::numeric_limits<double>::quiet_NaN();
1747+
m_last_tick = DateTime::NONE();
1748+
}
1749+
1750+
double compute() const
1751+
{
1752+
return m_ema;
1753+
}
1754+
1755+
private:
1756+
double m_ema;
1757+
double m_decay_factor;
1758+
DateTime m_last_tick;
1759+
};
1760+
1761+
class AdjustedHalflifeEMA
17271762
{
17281763
public:
1729-
HalflifeEMA() = default;
1764+
AdjustedHalflifeEMA() = default;
17301765

1731-
HalflifeEMA( TimeDelta halflife, DateTime start )
1766+
AdjustedHalflifeEMA( TimeDelta halflife, DateTime start, bool )
17321767
{
17331768
m_decay_factor = log( 0.5 ) / halflife.asNanoseconds();
17341769
m_last_tick = start;
17351770
reset();
17361771
}
17371772

1738-
HalflifeEMA( HalflifeEMA && rhs ) = default;
1739-
1740-
HalflifeEMA & operator=( HalflifeEMA && rhs ) = default;
1741-
17421773
void add( double x, DateTime now )
17431774
{
1744-
if( likely( !isnan( x ) ) )
1745-
{
1746-
TimeDelta delta_t = now - m_last_tick;
1747-
double decay = exp( m_decay_factor * delta_t.asNanoseconds() );
1748-
m_ema = decay * m_ema + x;
1749-
m_norm = decay * m_norm + 1.0;
1750-
m_last_tick = now;
1751-
}
1775+
TimeDelta delta_t = now - m_last_tick;
1776+
double decay = exp( m_decay_factor * delta_t.asNanoseconds() );
1777+
m_ema = decay * m_ema + x;
1778+
m_norm = decay * m_norm + 1.0;
1779+
m_last_tick = now;
17521780
}
17531781

17541782
void reset()
@@ -1758,7 +1786,7 @@ class HalflifeEMA
17581786

17591787
double compute() const
17601788
{
1761-
return m_ema / m_norm;
1789+
return likely( m_norm > 0 ) ? ( m_ema / m_norm ) : std::numeric_limits<double>::quiet_NaN();
17621790
}
17631791

17641792
private:
@@ -1767,35 +1795,37 @@ class HalflifeEMA
17671795
double m_norm;
17681796
double m_decay_factor;
17691797
DateTime m_last_tick;
1770-
17711798
};
17721799

17731800
class HalflifeDebiasEMA
17741801
{
17751802
public:
17761803
HalflifeDebiasEMA() = default;
17771804

1778-
HalflifeDebiasEMA( TimeDelta halflife, DateTime start )
1805+
HalflifeDebiasEMA( TimeDelta halflife, DateTime start, bool adjust )
17791806
{
1780-
m_decay_factor = log( 0.5 ) / halflife.asNanoseconds();
1807+
m_decay = log( 0.5 ) / halflife.asNanoseconds();
17811808
m_last_tick = start;
1809+
m_adjust = adjust;
17821810
reset();
17831811
}
17841812

1785-
HalflifeDebiasEMA( HalflifeDebiasEMA && rhs ) = default;
1786-
1787-
HalflifeDebiasEMA & operator=( HalflifeDebiasEMA && rhs ) = default;
1788-
17891813
void add( double x, DateTime now )
17901814
{
1791-
if( likely( !isnan( x ) ) )
1792-
{
1793-
TimeDelta delta_t = now - m_last_tick;
1794-
double decay = exp( m_decay_factor * delta_t.asNanoseconds() );
1795-
m_sqsum = decay * decay * m_sqsum + 1.0;
1796-
m_wsum = decay * m_wsum + 1.0;
1797-
m_last_tick = now;
1798-
}
1815+
TimeDelta delta_t = now - m_last_tick;
1816+
double decay_factor = exp( m_decay * delta_t.asNanoseconds() );
1817+
m_sqsum *= decay_factor * decay_factor;
1818+
m_wsum *= decay_factor;
1819+
1820+
double w0;
1821+
if( m_adjust )
1822+
w0 = 1.0;
1823+
else
1824+
w0 = 1 - m_decay;
1825+
m_sqsum += w0 * w0;
1826+
m_wsum += w0;
1827+
1828+
m_last_tick = now;
17991829
}
18001830

18011831
void reset()
@@ -1816,7 +1846,8 @@ class HalflifeDebiasEMA
18161846

18171847
double m_wsum;
18181848
double m_sqsum;
1819-
double m_decay_factor;
1849+
double m_decay;
1850+
bool m_adjust;
18201851
DateTime m_last_tick;
18211852
};
18221853

@@ -1841,10 +1872,22 @@ struct NanCheck
18411872
}
18421873
};
18431874

1875+
template <typename T, typename... Types>
1876+
struct is_any_of : std::false_type {};
1877+
1878+
template <typename T, typename First, typename... Rest>
1879+
struct is_any_of<T, First, Rest...> { static constexpr bool value = std::is_same_v<T, First> || is_any_of<T, Rest...>::value; };
1880+
1881+
template <typename T, typename... Types>
1882+
inline constexpr bool is_any_of_v = is_any_of<T, Types...>::value;
1883+
18441884
// Validates min_data_points and takes care of NaN handling
18451885
template<typename T>
18461886
class DataValidator
18471887
{
1888+
static constexpr bool PROCESS_NA = is_any_of_v<T, EMA, AdjustedEMA, AlphaDebiasEMA, Rank>;
1889+
static constexpr bool CONSIDER_NA = is_any_of_v<T, First, Last>;
1890+
18481891
public:
18491892
DataValidator() = default;
18501893

@@ -1865,7 +1908,7 @@ class DataValidator
18651908
if( isnan( x ) )
18661909
{
18671910
m_nans++;
1868-
if( m_process_na || ( m_consider_na && !m_igna ) )
1911+
if( PROCESS_NA || ( CONSIDER_NA && !m_igna ) )
18691912
m_stat.add( x );
18701913
}
18711914
else
@@ -1893,7 +1936,7 @@ class DataValidator
18931936
if( NanCheck::any_nan( args...) )
18941937
{
18951938
m_nans--;
1896-
if( m_process_na || ( m_consider_na && !m_igna ) )
1939+
if( PROCESS_NA || ( CONSIDER_NA && !m_igna ) )
18971940
m_stat.remove( args... );
18981941
}
18991942
else
@@ -1906,7 +1949,7 @@ class DataValidator
19061949
template<typename ...V>
19071950
double compute( V... args )
19081951
{
1909-
if( ( !m_igna && (m_nans > 0 && !m_consider_na ) ) || m_points < m_mdp )
1952+
if( ( !m_igna && (m_nans > 0 && !CONSIDER_NA ) ) || m_points < m_mdp )
19101953
return std::numeric_limits<double>::quiet_NaN();
19111954

19121955
return m_stat.compute( args... );
@@ -1933,9 +1976,6 @@ class DataValidator
19331976
int64_t m_mdp = 0;
19341977
bool m_igna = false;
19351978
T m_stat;
1936-
static constexpr bool m_process_na = ( std::is_same<T,EMA>::value || std::is_same<T,AdjustedEMA>::value || std::is_same<T,AlphaDebiasEMA>::value
1937-
|| std::is_same<T,HalflifeEMA>::value || std::is_same<T,HalflifeDebiasEMA>::value || std::is_same<T,Rank>::value );
1938-
static constexpr bool m_consider_na = ( std::is_same<T,First>::value || std::is_same<T,Last>::value );
19391979
};
19401980

19411981

cpp/csp/python/cspnpstatsimpl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ REGISTER_CPPNODE( csp::python, _np_weighted_kurt );
4747
// EMA nodes
4848
REGISTER_CPPNODE( csp::python, _np_ema_compute );
4949
REGISTER_CPPNODE( csp::python, _np_ema_adjusted );
50-
REGISTER_CPPNODE( csp::python, _np_ema_timewise );
51-
REGISTER_CPPNODE( csp::python, _np_ema_debias_alpha );
52-
REGISTER_CPPNODE( csp::python, _np_ema_debias_halflife );
50+
REGISTER_CPPNODE( csp::python, _np_ema_halflife );
51+
REGISTER_CPPNODE( csp::python, _np_ema_halflife_adjusted );
52+
REGISTER_CPPNODE( csp::python, _np_ema_alpha_debias );
53+
REGISTER_CPPNODE( csp::python, _np_ema_halflife_debias );
5354

5455

5556
static PyModuleDef _cspnpstatsimpl_module = {

cpp/csp/python/cspstatsimpl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ REGISTER_CPPNODE( csp::cppnodes, _weighted_kurt );
4040
// EMA nodes
4141
REGISTER_CPPNODE( csp::cppnodes, _ema_compute );
4242
REGISTER_CPPNODE( csp::cppnodes, _ema_adjusted );
43-
REGISTER_CPPNODE( csp::cppnodes, _ema_timewise );
44-
REGISTER_CPPNODE( csp::cppnodes, _ema_debias_alpha );
45-
REGISTER_CPPNODE( csp::cppnodes, _ema_debias_halflife );
43+
REGISTER_CPPNODE( csp::cppnodes, _ema_halflife );
44+
REGISTER_CPPNODE( csp::cppnodes, _ema_halflife_adjusted );
45+
REGISTER_CPPNODE( csp::cppnodes, _ema_alpha_debias );
46+
REGISTER_CPPNODE( csp::cppnodes, _ema_halflife_debias );
4647

4748
static PyModuleDef _cspstatsimpl_module = {
4849
PyModuleDef_HEAD_INIT,

cpp/csp/python/npstatsimpl.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ EXPORT_TEMPLATE_CPPNODE( _np_rank, SINGLE_ARG( _npComputeTwoArg<int
681681
EXPORT_TEMPLATE_CPPNODE( _np_kurt, SINGLE_ARG( _npComputeTwoArg<bool, Kurtosis> ) );
682682
EXPORT_TEMPLATE_CPPNODE( _np_ema_compute, _npComputeEMA<EMA> );
683683
EXPORT_TEMPLATE_CPPNODE( _np_ema_adjusted, _npComputeEMA<AdjustedEMA>);
684-
EXPORT_TEMPLATE_CPPNODE( _np_ema_debias_alpha, _npComputeEMA<AlphaDebiasEMA> );
684+
EXPORT_TEMPLATE_CPPNODE( _np_ema_alpha_debias, _npComputeEMA<AlphaDebiasEMA> );
685685

686686
// Bivariate
687687
template<typename C>
@@ -914,10 +914,12 @@ EXPORT_CPPNODE ( _np_quantile );
914914

915915
// C: computation class
916916
template<typename C>
917-
DECLARE_CPPNODE( _np_exp_timewise )
917+
DECLARE_CPPNODE( _np_exp_halflife )
918918
{
919919
TS_INPUT( PyObjectPtr, x );
920920
SCALAR_INPUT( TimeDelta, halflife );
921+
SCALAR_INPUT( bool, adjust );
922+
921923
TS_INPUT( Generic, trigger );
922924
TS_INPUT( Generic, sampler );
923925
TS_INPUT( Generic, reset );
@@ -929,7 +931,7 @@ DECLARE_CPPNODE( _np_exp_timewise )
929931

930932
TS_OUTPUT( PyObjectPtr );
931933

932-
INIT_CPPNODE( _np_exp_timewise ) { }
934+
INIT_CPPNODE( _np_exp_halflife ) { }
933935

934936
INVOKE()
935937
{
@@ -948,7 +950,7 @@ DECLARE_CPPNODE( _np_exp_timewise )
948950
s_elem.reserve( s_shp.m_n );
949951
for( int64_t j = 0; j < s_shp.m_n; j++ )
950952
{
951-
s_elem.emplace_back( DataValidator<C>( min_data_points, true, halflife, now() - TimeDelta::fromMicroseconds( 1 ) ) );
953+
s_elem.emplace_back( DataValidator<C>( min_data_points, true, halflife, now() - TimeDelta::fromMicroseconds( 1 ), adjust ) );
952954
}
953955
s_first = false;
954956
}
@@ -964,8 +966,9 @@ DECLARE_CPPNODE( _np_exp_timewise )
964966
}
965967
};
966968

967-
EXPORT_TEMPLATE_CPPNODE( _np_ema_timewise, _np_exp_timewise<HalflifeEMA> );
968-
EXPORT_TEMPLATE_CPPNODE( _np_ema_debias_halflife, _np_exp_timewise<HalflifeDebiasEMA> );
969+
EXPORT_TEMPLATE_CPPNODE( _np_ema_halflife, _np_exp_halflife<HalflifeEMA> );
970+
EXPORT_TEMPLATE_CPPNODE( _np_ema_halflife_adjusted, _np_exp_halflife<AdjustedHalflifeEMA> );
971+
EXPORT_TEMPLATE_CPPNODE( _np_ema_halflife_debias, _np_exp_halflife<HalflifeDebiasEMA> );
969972

970973
template<typename C>
971974
DECLARE_CPPNODE( _np_matrix_compute )

0 commit comments

Comments
 (0)