Skip to content

Commit 52e088e

Browse files
committed
Add allow_non_overlapping parameter for bivariate statistics cov, corr and ema_cov
Signed-off-by: Adam Glustein <adamglustein@gmail.com> Signed-off-by: Adam Glustein <adam.glustein@point72.com>
1 parent 0e1dad5 commit 52e088e

5 files changed

Lines changed: 152 additions & 64 deletions

File tree

cpp/csp/cppnodes/statsimpl.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,33 @@ DECLARE_CPPNODE( _in_sequence_check )
108108

109109
EXPORT_CPPNODE( _in_sequence_check );
110110

111+
/*
112+
@csp.node
113+
def _discard_non_overlapping(x: ts[float], y: ts[float]):
114+
*/
115+
116+
DECLARE_CPPNODE( _discard_non_overlapping )
117+
{
118+
TS_INPUT( double, x );
119+
TS_INPUT( double, y );
120+
121+
TS_NAMED_OUTPUT( double, x_sync );
122+
TS_NAMED_OUTPUT( double, y_sync );
123+
124+
INIT_CPPNODE( _discard_non_overlapping ) { }
125+
126+
INVOKE()
127+
{
128+
if( csp.ticked( x ) && csp.ticked( y ) )
129+
{
130+
x_sync.output( x );
131+
y_sync.output( y );
132+
}
133+
};
134+
};
135+
136+
EXPORT_CPPNODE( _discard_non_overlapping );
137+
111138
/*
112139
@csp.node(cppimpl=_cspstatsimpl._sync_nan_f)
113140
def _sync_nan_f(x: ts[float], y: ts[float]) -> csp.Outputs(x_sync=ts[float], y_sync=ts[float]):

cpp/csp/python/cspstatsimpl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ REGISTER_CPPNODE( csp::cppnodes, _time_window_updates );
77
REGISTER_CPPNODE( csp::cppnodes, _cross_sectional_as_list );
88
REGISTER_CPPNODE( csp::cppnodes, _min_hit_by_tick );
99
REGISTER_CPPNODE( csp::cppnodes, _in_sequence_check );
10+
REGISTER_CPPNODE( csp::cppnodes, _discard_non_overlapping );
1011
REGISTER_CPPNODE( csp::cppnodes, _sync_nan_f );
1112

1213
// Base statistics

csp/stats.py

Lines changed: 72 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ def _in_sequence_check(x: ts["T"], y: ts["T"]):
151151
raise NotImplementedError("_in_sequence_check only implemented in C++")
152152

153153

154+
@csp.node(cppimpl=_cspstatsimpl._discard_non_overlapping)
155+
def _discard_non_overlapping(x: ts[float], y: ts[float]) -> csp.Outputs(x_sync=ts[float], y_sync=ts[float]):
156+
raise NotImplementedError("_discard_non_overlapping only implemented in C++")
157+
return csp.output(x_sync=0, y_sync=0)
158+
159+
154160
@csp.node(cppimpl=_cspstatsimpl._sync_nan_f)
155161
def _sync_nan_f(x: ts[float], y: ts[float]) -> csp.Outputs(x_sync=ts[float], y_sync=ts[float]):
156162
raise NotImplementedError("_sync_nan_f only implemented in C++")
@@ -176,12 +182,6 @@ def _combine_signal(x: ts["T"], y: ts["U"]) -> ts[bool]:
176182
return True
177183

178184

179-
@csp.node
180-
def _combine_signal(x: ts["T"], y: ts["U"]) -> ts[bool]:
181-
if csp.ticked(x, y):
182-
return True
183-
184-
185185
@csp.node
186186
def _np_log(x: ts[np.ndarray]) -> ts[np.ndarray]:
187187
return np.log(x)
@@ -280,21 +280,31 @@ def _setup(x, interval, min_window, trigger, sampler, reset, weights=None, ignor
280280
return series, interval, min_window, trigger, min_hit, updates, sampler, reset, weights, recalc, clear_stat
281281

282282

283+
def _synchronize_bivariate(x, y, allow_non_overlapping):
284+
"""
285+
If allow_non_overlapping=True, discard any out-of-sync ticks between and y. Else, raise an exception when this occurs.
286+
"""
287+
if x is not y:
288+
if allow_non_overlapping:
289+
sync = _discard_non_overlapping(x, y)
290+
x, y = sync.x_sync, sync.y_sync
291+
else:
292+
_in_sequence_check(x, y)
293+
return x, y
294+
295+
283296
def _bivariate_setup(
284-
x, y, interval, min_window, trigger, sampler, reset, weights=None, ignore_weights=False, recalc=None
297+
x, y, interval, min_window, trigger, sampler, reset, weights=None, recalc=None, allow_non_overlapping=False
285298
):
286299
"""
287300
Sets up time-series window updates and triggers for a bivariate stats calculation
288301
"""
302+
x, y = _synchronize_bivariate(x, y, allow_non_overlapping)
289303
x_upd = _setup(x, interval, min_window, trigger, sampler, reset, weights, False, recalc)[5]
290304
series, interval, min_window, trigger, min_hit, y_upd, sampler, reset, weights, recalc, clear_stat = _setup(
291305
y, interval, min_window, trigger, sampler, reset, weights, False, recalc
292306
)
293307

294-
in_seq = None
295-
if x is not y:
296-
in_seq = _in_sequence_check(x, y)
297-
298308
return (
299309
series,
300310
interval,
@@ -308,7 +318,6 @@ def _bivariate_setup(
308318
weights,
309319
recalc,
310320
clear_stat,
311-
in_seq,
312321
)
313322

314323

@@ -2181,25 +2190,27 @@ def cov(
21812190
reset: ts[object] = None,
21822191
recalc: ts[object] = None,
21832192
min_data_points: int = 0,
2193+
allow_non_overlapping: bool = False,
21842194
) -> ts[Union[float, np.ndarray]]:
21852195
"""
21862196
21872197
Returns the covariance between two in-sequence time-series within the given window. If the time-series are of type np.ndarray, the covariance is calculated elementwise.
21882198
21892199
Inputs
2190-
x: time series data, of type float or np.ndarray
2191-
y: time series data, of type float or np.ndarray, which ticks at the same time as x
2192-
interval: the window interval (either time or tick specified)
2193-
min_window: the minimum window (either time or tick specified) before statistics are returned. Must be the same type as interval
2194-
ddof: delta degrees of freedom
2195-
ignore_na: if True, will treat NaN values as missing data. If False, a NaN present in the window will make the computed statistic NaN as well
2196-
trigger: another time-series which specifies when you want to recalculate the statistic
2197-
weights: another time-series which specifies the weights to use on each x value, if a weighted covariance is desired
2198-
sampler: another time-series which specifies when x should tick. If x ticks when sampler does not, the data is ignored. If sampler ticks when x does not,
2199-
the data point is treated as NaN
2200-
reset: another time-series which will clear the data in the window when it ticks
2201-
recalc: another time-series which triggers a clean recalculation of the window statistic, and in doing so clears any accumulated floating-point error
2202-
min_data_points: minimum number of current ticks in the interval needed for a valid computation. If there are fewer ticks, NaN is returned.
2200+
x: time series data, of type float or np.ndarray
2201+
y: time series data, of type float or np.ndarray, which ticks at the same time as x
2202+
interval: the window interval (either time or tick specified)
2203+
min_window: the minimum window (either time or tick specified) before statistics are returned. Must be the same type as interval
2204+
ddof: delta degrees of freedom
2205+
ignore_na: if True, will treat NaN values as missing data. If False, a NaN present in the window will make the computed statistic NaN as well
2206+
trigger: another time-series which specifies when you want to recalculate the statistic
2207+
weights: another time-series which specifies the weights to use on each x value, if a weighted covariance is desired
2208+
sampler: another time-series which specifies when x should tick. If x ticks when sampler does not, the data is ignored. If sampler ticks when x does not,
2209+
the data point is treated as NaN
2210+
reset: another time-series which will clear the data in the window when it ticks
2211+
recalc: another time-series which triggers a clean recalculation of the window statistic, and in doing so clears any accumulated floating-point error
2212+
min_data_points: minimum number of current ticks in the interval needed for a valid computation. If there are fewer ticks, NaN is returned.
2213+
allow_non_overlapping: if True, discard any ticks of x and y that occur out-of-sync with one another. If False, raise an exception on any out-of-sync ticks.
22032214
22042215
"""
22052216

@@ -2216,8 +2227,7 @@ def cov(
22162227
weights,
22172228
recalc,
22182229
clear_stat,
2219-
in_seq,
2220-
) = _bivariate_setup(x, y, interval, min_window, trigger, sampler, reset, weights, True, recalc)
2230+
) = _bivariate_setup(x, y, interval, min_window, trigger, sampler, reset, weights, recalc, allow_non_overlapping)
22212231

22222232
# Use same "debiasing" for weighted/non-weighted
22232233
edge = None
@@ -2543,24 +2553,26 @@ def corr(
25432553
reset: ts[object] = None,
25442554
recalc: ts[object] = None,
25452555
min_data_points: int = 0,
2556+
allow_non_overlapping: bool = False,
25462557
) -> ts[Union[float, np.ndarray]]:
25472558
"""
25482559
25492560
Returns the correlation between x and y within the given window. If the time-series are of type np.ndarray, the correlation is calculated elementwise.
25502561
25512562
Inputs
2552-
x: time series data, of type float or np.ndarray
2553-
y: time series data, of type float or np.ndarray, which ticks at the same time x ticks
2554-
interval: the window interval (either time or tick specified)
2555-
min_window: the minimum window (either time or tick specified) before statistics are returned. Must be the same type as interval
2556-
ignore_na: if True, will treat NaN values as missing data. If False, a NaN present in the window will make the computed statistic NaN as well
2557-
trigger: another time-series which specifies when you want to recalculate the statistic
2558-
weights: another time-series which specifies the weights to use on each x value, if a weighted correlation is desired
2559-
sampler: another time-series which specifies when x should tick. If x ticks when sampler does not, the data is ignored. If sampler ticks when x does not,
2560-
the data point is treated as NaN
2561-
reset: another time-series which will clear the data in the window when it ticks
2562-
recalc: another time-series which triggers a clean recalculation of the window statistic, and in doing so clears any accumulated floating-point error
2563-
min_data_points: minimum number of current ticks in the interval needed for a valid computation. If there are fewer ticks, NaN is returned.
2563+
x: time series data, of type float or np.ndarray
2564+
y: time series data, of type float or np.ndarray, which ticks at the same time x ticks
2565+
interval: the window interval (either time or tick specified)
2566+
min_window: the minimum window (either time or tick specified) before statistics are returned. Must be the same type as interval
2567+
ignore_na: if True, will treat NaN values as missing data. If False, a NaN present in the window will make the computed statistic NaN as well
2568+
trigger: another time-series which specifies when you want to recalculate the statistic
2569+
weights: another time-series which specifies the weights to use on each x value, if a weighted correlation is desired
2570+
sampler: another time-series which specifies when x should tick. If x ticks when sampler does not, the data is ignored. If sampler ticks when x does not,
2571+
the data point is treated as NaN
2572+
reset: another time-series which will clear the data in the window when it ticks
2573+
recalc: another time-series which triggers a clean recalculation of the window statistic, and in doing so clears any accumulated floating-point error
2574+
min_data_points: minimum number of current ticks in the interval needed for a valid computation. If there are fewer ticks, NaN is returned.
2575+
allow_non_overlapping: if True, discard any ticks of x and y that occur out-of-sync with one another. If False, raise an exception on any out-of-sync ticks.
25642576
25652577
"""
25662578
(
@@ -2576,8 +2588,7 @@ def corr(
25762588
weights,
25772589
recalc,
25782590
clear_stat,
2579-
in_seq,
2580-
) = _bivariate_setup(x, y, interval, min_window, trigger, sampler, reset, weights, True, recalc)
2591+
) = _bivariate_setup(x, y, interval, min_window, trigger, sampler, reset, weights, recalc, allow_non_overlapping)
25812592

25822593
if series.tstype.typ is float:
25832594
if weights is not None:
@@ -2965,38 +2976,40 @@ def ema_cov(
29652976
reset: ts[object] = None,
29662977
recalc: ts[object] = None,
29672978
min_data_points: int = 0,
2979+
allow_non_overlapping: bool = False,
29682980
) -> ts[Union[float, np.ndarray]]:
29692981
"""
29702982
29712983
Returns the exponential moving covariance between two time series.
29722984
29732985
Inputs
2974-
x: time series data, of type float or np.ndarray
2975-
y: time series data, of type float or np.ndarray, which ticks at the same time as x
2976-
min_periods: the minimum number of data points before statistics are returned
2977-
alpha: specify the decay parameter in terms of alpha
2978-
span: specify the decay parameter in terms of span
2979-
com: specify the decay parameter in terms of com
2980-
halflife: specify the decay parameter in terms of halflife
2981-
adjust: if True, an adjusted EMA will be computed. If False, a standard (unadjusted) EMA will be computed
2982-
horizon: if specified, values that are older than the horizon will be removed entirely from the computation (essentially making EMA a window computation)
2983-
bias: if True, a biased EMA covariance is computed. If False, the covariance estimate is unbiased
2984-
ignore_na: if True, NaNs will be ignored and have no effect on the computation. If False, a NaN will shift the observation window once new non-NaN data comes in
2985-
trigger: another time-series which specifies when you want to recalculate the statistic
2986-
sampler: another time-series which specifies when x should tick. If x ticks when sampler does not, the data is ignored. If sampler ticks when x does not,
2987-
the data point is treated as NaN
2988-
reset: another time-series which will clear the data in the window when it ticks
2989-
recalc: only valid when a finite-horizon EMA is used. Another time-series which triggers a clean recalculation of the window statistic, and in doing so clears any accumulated floating-point error
2990-
min_data_points: minimum number of current ticks in the interval needed for a valid computation. If there are fewer ticks, NaN is returned.
2986+
x: time series data, of type float or np.ndarray
2987+
y: time series data, of type float or np.ndarray, which ticks at the same time as x
2988+
min_periods: the minimum number of data points before statistics are returned
2989+
alpha: specify the decay parameter in terms of alpha
2990+
span: specify the decay parameter in terms of span
2991+
com: specify the decay parameter in terms of com
2992+
halflife: specify the decay parameter in terms of halflife
2993+
adjust: if True, an adjusted EMA will be computed. If False, a standard (unadjusted) EMA will be computed
2994+
horizon: if specified, values that are older than the horizon will be removed entirely from the computation (essentially making EMA a window computation)
2995+
bias: if True, a biased EMA covariance is computed. If False, the covariance estimate is unbiased
2996+
ignore_na: if True, NaNs will be ignored and have no effect on the computation. If False, a NaN will shift the observation window once new non-NaN data comes in
2997+
trigger: another time-series which specifies when you want to recalculate the statistic
2998+
sampler: another time-series which specifies when x should tick. If x ticks when sampler does not, the data is ignored. If sampler ticks when x does not,
2999+
the data point is treated as NaN
3000+
reset: another time-series which will clear the data in the window when it ticks
3001+
recalc: only valid when a finite-horizon EMA is used. Another time-series which triggers a clean recalculation of the window statistic, and in doing so clears any accumulated floating-point error
3002+
min_data_points: minimum number of current ticks in the interval needed for a valid computation. If there are fewer ticks, NaN is returned.
3003+
allow_non_overlapping: if True, discard any ticks of x and y that occur out-of-sync with one another. If False, raise an exception on any out-of-sync ticks.
29913004
29923005
"""
29933006

29943007
alpha, interval, min_window, debias_horizon, recalc = _validate_ema(
29953008
alpha, span, com, halflife, adjust, horizon, recalc
29963009
)
29973010

3011+
x, y = _synchronize_bivariate(x, y, allow_non_overlapping)
29983012
if x is not y:
2999-
_in_sequence_check(x, y)
30003013
sync = _sync_nan(x, y)
30013014
x, y = sync.x_sync, sync.y_sync
30023015

csp/tests/test_stats.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3599,6 +3599,47 @@ def g():
35993599
self.assertTrue(pd.Series(res["kurt"][1]).isna().all())
36003600
self.assertTrue(pd.Series(res["corr"][1]).isna().all())
36013601

3602+
def test_allow_non_overlapping_bivariate(self):
3603+
st = datetime(2020, 1, 1)
3604+
3605+
@csp.graph
3606+
def g(allow_non_overlapping: bool):
3607+
x = csp.curve(
3608+
typ=float,
3609+
data=[
3610+
(datetime(2020, 1, 1), 1),
3611+
(datetime(2020, 1, 2), 2),
3612+
(datetime(2020, 1, 4), 4), # discarded
3613+
(datetime(2020, 1, 6), 6), # discarded
3614+
(datetime(2020, 1, 7), 7),
3615+
],
3616+
)
3617+
y = csp.curve(
3618+
typ=float,
3619+
data=[
3620+
(datetime(2020, 1, 1), 1),
3621+
(datetime(2020, 1, 2), 2),
3622+
(datetime(2020, 1, 3), -1), # discarded
3623+
(datetime(2020, 1, 5), -2), # discarded
3624+
(datetime(2020, 1, 7), 7),
3625+
],
3626+
)
3627+
cov = csp.stats.cov(x, y, 5, 1, allow_non_overlapping=allow_non_overlapping)
3628+
corr = csp.stats.corr(x, y, 5, 1, allow_non_overlapping=allow_non_overlapping)
3629+
ema_cov = csp.stats.ema_cov(x, y, 1, alpha=0.1, allow_non_overlapping=allow_non_overlapping)
3630+
csp.add_graph_output("cov", cov)
3631+
csp.add_graph_output("corr", corr)
3632+
csp.add_graph_output("ema_cov", ema_cov)
3633+
3634+
res = csp.run(g, True, starttime=st, endtime=timedelta(days=8), output_numpy=True)
3635+
for output in res.values():
3636+
# Convert expected datetimes to the same type as output
3637+
expected_dates = pd.to_datetime([datetime(2020, 1, i) for i in (1, 2, 7)])
3638+
np.testing.assert_array_equal(output[0], expected_dates.values)
3639+
3640+
# Additional sanity check is that correlation should be 1 as middle ticks are ignored
3641+
np.testing.assert_allclose(res["corr"][1], np.array([np.nan, 1.0, 1.0]), equal_nan=True)
3642+
36023643

36033644
if __name__ == "__main__":
36043645
unittest.main()

0 commit comments

Comments
 (0)