zigzag-reload 0.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ v0.1.0, 2014-02-21 -- Initial Release, refactored version of my poorly
2
+ executed TSGeom package.
3
+ v0.1.1, 2014-02-25 -- Change README.txt to README.rst so that Github will
4
+ process it correctly.
5
+ v0.1.2, 2014-03-20 -- FIX bug that caused install error due to changed README
6
+ file name.
7
+ v0.1.3, 2014-03-20 -- FIX of previous FIX. Apparently, I don't understand the
8
+ README.rst usage pattern.
9
+ v0.1.4, 2017-05-09 -- Refactor for Cython instead of numba
10
+ v0.3.0, 2022-07-31 -- Switch to poetry for package management. Drop support
11
+ for Python below 3.8. Add plotting package helpers.
12
+ Add support for Pandas.
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2014, John Bjorn Nelson
2
+
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8
+
9
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
10
+
11
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.1
2
+ Name: zigzag-reload
3
+ Version: 0.3.3
4
+ Summary: Package for finding peaks and valleys of time series.
5
+ Home-page: https://github.com/jbn/ZigZag
6
+ License: BSD-3-Clause
7
+ Author: generativist
8
+ Author-email: jbn@abreka.com
9
+ Requires-Python: >=3.8
10
+ Classifier: License :: OSI Approved :: BSD License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Dist: Cython (>=0.29,<0.30)
18
+ Requires-Dist: numpy (>=1.21.1)
19
+ Project-URL: Repository, https://github.com/jbn/ZigZag
20
+ Description-Content-Type: text/markdown
21
+
22
+ ![build-and-test-python](https://github.com/jbn/ZigZag/actions/workflows/build-and-test-python.yml/badge.svg)
23
+ [![PyPI version](https://badge.fury.io/py/ZigZag.svg)](https://badge.fury.io/py/ZigZag)
24
+ [![GitHub stars](https://img.shields.io/github/stars/jbn/ZigZag)](https://github.com/jbn/ZigZag/stargazers)
25
+ [![GitHub license](https://img.shields.io/github/license/jbn/ZigZag)](https://github.com/jbn/ZigZag/blob/main/LICENSE.txt)
26
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/ZigZag)
27
+
28
+
29
+ # ZigZag
30
+
31
+ ZigZag provides functions for identifying the peaks and valleys of a time
32
+ series. Additionally, it provides a function for computing the maximum drawdown.
33
+
34
+ For fastest understanding, [view the IPython notebook demo tutorial](https://github.com/jbn/ZigZag/blob/master/zigzag_demo.ipynb>).
35
+
36
+ ## Contributing
37
+
38
+ This is an admittedly small project. Still, if you have any contributions,
39
+ please [fork this project on github](https://github.com/jbn/ZigZag) and
40
+ send me a pull request.
41
+
@@ -0,0 +1,19 @@
1
+ ![build-and-test-python](https://github.com/jbn/ZigZag/actions/workflows/build-and-test-python.yml/badge.svg)
2
+ [![PyPI version](https://badge.fury.io/py/ZigZag.svg)](https://badge.fury.io/py/ZigZag)
3
+ [![GitHub stars](https://img.shields.io/github/stars/jbn/ZigZag)](https://github.com/jbn/ZigZag/stargazers)
4
+ [![GitHub license](https://img.shields.io/github/license/jbn/ZigZag)](https://github.com/jbn/ZigZag/blob/main/LICENSE.txt)
5
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/ZigZag)
6
+
7
+
8
+ # ZigZag
9
+
10
+ ZigZag provides functions for identifying the peaks and valleys of a time
11
+ series. Additionally, it provides a function for computing the maximum drawdown.
12
+
13
+ For fastest understanding, [view the IPython notebook demo tutorial](https://github.com/jbn/ZigZag/blob/master/zigzag_demo.ipynb>).
14
+
15
+ ## Contributing
16
+
17
+ This is an admittedly small project. Still, if you have any contributions,
18
+ please [fork this project on github](https://github.com/jbn/ZigZag) and
19
+ send me a pull request.
@@ -0,0 +1,55 @@
1
+ import os
2
+ import shutil
3
+ import sys
4
+ from distutils.command.build_ext import build_ext
5
+ from distutils.core import Distribution, Extension
6
+
7
+ from Cython.Build import cythonize
8
+ import numpy as np
9
+
10
+ compile_args = ["-O3"]
11
+ link_args = []
12
+ include_dirs = [np.get_include()]
13
+ libraries = ["m"]
14
+
15
+
16
+ def build():
17
+ debug_mode_on = '1' if 'debug_mode_on' in os.environ else '0'
18
+ extensions = [
19
+ Extension(
20
+ "*",
21
+ ["zigzag/*.pyx"],
22
+ extra_compile_args=compile_args,
23
+ extra_link_args=link_args,
24
+ include_dirs=include_dirs,
25
+ libraries=libraries if os.name != 'nt' else [],
26
+ define_macros=[('CYTHON_TRACE', debug_mode_on),
27
+ ('CYTHON_TRACE_NOGIL', debug_mode_on),
28
+ ('CYTHON_BINDING', debug_mode_on),
29
+ ('CYTHON_FAST_PYCCALL', '1')],
30
+ )
31
+ ]
32
+ ext_modules = cythonize(
33
+ extensions,
34
+ include_path=include_dirs,
35
+ compiler_directives={"binding": True, "language_level": sys.version_info.major},
36
+ )
37
+
38
+ distribution = Distribution({"name": "extended", "ext_modules": ext_modules})
39
+ distribution.package_dir = "extended"
40
+
41
+ cmd = build_ext(distribution)
42
+ cmd.ensure_finalized()
43
+ cmd.run()
44
+
45
+ # Copy built extensions back to the project
46
+ for output in cmd.get_outputs():
47
+ relative_extension = os.path.relpath(output, cmd.build_lib)
48
+ shutil.copyfile(output, relative_extension)
49
+ mode = os.stat(relative_extension).st_mode
50
+ mode |= (mode & 0o444) >> 2
51
+ os.chmod(relative_extension, mode)
52
+
53
+
54
+ if __name__ == "__main__":
55
+ build()
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = [
3
+ "poetry-core>=1.0.0",
4
+ "Cython>=0.29",
5
+ "numpy >=1.21.1",
6
+ ]
7
+ build-backend = "poetry.core.masonry.api"
8
+
9
+ [[tool.poetry.source]]
10
+ name = "tsinghua"
11
+ url = "https://pypi.tuna.tsinghua.edu.cn/simple"
12
+ default = true
13
+
14
+ [tool.poetry]
15
+ name = "zigzag-reload"
16
+ version = "0.3.3"
17
+ readme = "README.md"
18
+ homepage = "https://github.com/jbn/ZigZag"
19
+ repository = "https://github.com/jbn/ZigZag"
20
+ description = "Package for finding peaks and valleys of time series."
21
+ authors = ["generativist <jbn@abreka.com>"]
22
+ license = "BSD-3-Clause"
23
+ packages = [{ include = "zigzag" }]
24
+ include = [
25
+ { path = "zigzag/**/*.so", format = "wheel" },
26
+ { path = "zigzag/**/*.pyx" },
27
+ { path = "zigzag/**/*.pyd" },
28
+ "CHANGES.txt"
29
+ ]
30
+
31
+ [tool.poetry.dependencies]
32
+ python = ">=3.8"
33
+ numpy = ">=1.21.1"
34
+ Cython = "^0.29"
35
+
36
+ [tool.poetry.build]
37
+ generate-setup-file = false
38
+ script = "build.py"
39
+
40
+ [tool.poetry.dev-dependencies]
41
+ pytest = "^7.1.2"
42
+ pandas = ">=0.22,<2.0"
43
+
44
+ [tool.pytest.ini_options]
45
+ minversion = "6.0"
46
+ addopts = "-ra -q"
47
+ testpaths = [
48
+ "tests",
49
+ ]
@@ -0,0 +1,4 @@
1
+ from zigzag.core import *
2
+
3
+ PEAK = 1
4
+ VALLEY = -1
@@ -0,0 +1,234 @@
1
+ cimport cython
2
+ import numpy as np
3
+ from numpy cimport ndarray, int_t
4
+
5
+ DEF PEAK = 1
6
+ DEF VALLEY = -1
7
+
8
+
9
+ @cython.boundscheck(False)
10
+ @cython.wraparound(False)
11
+ cpdef int_t identify_initial_pivot(double [:] X,
12
+ double up_thresh,
13
+ double down_thresh):
14
+ cdef:
15
+ double x_0 = X[0]
16
+ double x_t = x_0
17
+
18
+ double max_x = x_0
19
+ double min_x = x_0
20
+
21
+ int_t max_t = 0
22
+ int_t min_t = 0
23
+
24
+ up_thresh += 1
25
+ down_thresh += 1
26
+
27
+ for t in range(1, len(X)):
28
+ x_t = X[t]
29
+
30
+ if x_t / min_x >= up_thresh:
31
+ return VALLEY if min_t == 0 else PEAK
32
+
33
+ if x_t / max_x <= down_thresh:
34
+ return PEAK if max_t == 0 else VALLEY
35
+
36
+ if x_t > max_x:
37
+ max_x = x_t
38
+ max_t = t
39
+
40
+ if x_t < min_x:
41
+ min_x = x_t
42
+ min_t = t
43
+
44
+ t_n = len(X)-1
45
+ return VALLEY if x_0 < X[t_n] else PEAK
46
+
47
+ def _to_ndarray(X):
48
+ # The type signature in peak_valley_pivots_detailed does not work for
49
+ # pandas series because as of 0.13.0 it no longer sub-classes ndarray.
50
+ # The workaround everyone used was to call `.values` directly before
51
+ # calling the function. Which is fine but a little annoying.
52
+ t = type(X)
53
+ if t.__name__ == 'ndarray':
54
+ pass # Check for ndarray first for historical reasons
55
+ elif f"{t.__module__}.{t.__name__}" == 'pandas.core.series.Series':
56
+ X = X.values
57
+ elif isinstance(X, (list, tuple)):
58
+ X = np.array(X)
59
+
60
+ return X
61
+
62
+
63
+ def peak_valley_pivots(X, up_thresh, down_thresh):
64
+ X = _to_ndarray(X)
65
+
66
+ # Ensure float for correct signature
67
+ if not str(X.dtype).startswith('float'):
68
+ X = X.astype(np.float64)
69
+
70
+ return peak_valley_pivots_detailed(X, up_thresh, down_thresh, True, False)
71
+
72
+
73
+ @cython.boundscheck(False)
74
+ @cython.wraparound(False)
75
+ cpdef peak_valley_pivots_detailed(double [:] X,
76
+ double up_thresh,
77
+ double down_thresh,
78
+ bint limit_to_finalized_segments,
79
+ bint use_eager_switching_for_non_final):
80
+ """
81
+ Find the peaks and valleys of a series.
82
+
83
+ :param X: the series to analyze
84
+ :param up_thresh: minimum relative change necessary to define a peak
85
+ :param down_thesh: minimum relative change necessary to define a valley
86
+ :return: an array with 0 indicating no pivot and -1 and 1 indicating
87
+ valley and peak
88
+
89
+
90
+ The First and Last Elements
91
+ ---------------------------
92
+ The first and last elements are guaranteed to be annotated as peak or
93
+ valley even if the segments formed do not have the necessary relative
94
+ changes. This is a tradeoff between technical correctness and the
95
+ propensity to make mistakes in data analysis. The possible mistake is
96
+ ignoring data outside the fully realized segments, which may bias
97
+ analysis.
98
+ """
99
+ if down_thresh > 0:
100
+ raise ValueError('The down_thresh must be negative.')
101
+
102
+ cdef:
103
+ int_t initial_pivot = identify_initial_pivot(X,
104
+ up_thresh,
105
+ down_thresh)
106
+ int_t t_n = len(X)
107
+ ndarray[int_t, ndim=1] pivots = np.zeros(t_n, dtype=np.int_)
108
+ int_t trend = -initial_pivot
109
+ int_t last_pivot_t = 0
110
+ double last_pivot_x = X[0]
111
+ double x, r
112
+
113
+ pivots[0] = initial_pivot
114
+
115
+ # Adding one to the relative change thresholds saves operations. Instead
116
+ # of computing relative change at each point as x_j / x_i - 1, it is
117
+ # computed as x_j / x_1. Then, this value is compared to the threshold + 1.
118
+ # This saves (t_n - 1) subtractions.
119
+ up_thresh += 1
120
+ down_thresh += 1
121
+
122
+ for t in range(1, t_n):
123
+ x = X[t]
124
+ r = x / last_pivot_x
125
+
126
+ if trend == -1:
127
+ if r >= up_thresh:
128
+ pivots[last_pivot_t] = trend
129
+ trend = PEAK
130
+ last_pivot_x = x
131
+ last_pivot_t = t
132
+ elif x < last_pivot_x:
133
+ last_pivot_x = x
134
+ last_pivot_t = t
135
+ else:
136
+ if r <= down_thresh:
137
+ pivots[last_pivot_t] = trend
138
+ trend = VALLEY
139
+ last_pivot_x = x
140
+ last_pivot_t = t
141
+ elif x > last_pivot_x:
142
+ last_pivot_x = x
143
+ last_pivot_t = t
144
+
145
+
146
+ if limit_to_finalized_segments:
147
+ if use_eager_switching_for_non_final:
148
+ if last_pivot_t > 0 and last_pivot_t < t_n-1:
149
+ pivots[last_pivot_t] = trend
150
+ pivots[t_n-1] = -trend
151
+ else:
152
+ pivots[t_n-1] = trend
153
+ else:
154
+ if last_pivot_t == t_n-1:
155
+ pivots[last_pivot_t] = trend
156
+ elif pivots[t_n-1] == 0:
157
+ pivots[t_n-1] = -trend
158
+
159
+ return pivots
160
+
161
+
162
+ def max_drawdown(X) -> float:
163
+ X = _to_ndarray(X)
164
+
165
+ # Ensure float for correct signature
166
+ if not str(X.dtype).startswith('float'):
167
+ X = X.astype(np.float64)
168
+
169
+ return max_drawdown_c(X)
170
+
171
+
172
+ @cython.boundscheck(False)
173
+ @cython.wraparound(False)
174
+ cpdef double max_drawdown_c(ndarray[double, ndim=1] X):
175
+ """
176
+ Compute the maximum drawdown of some sequence.
177
+
178
+ :return: 0 if the sequence is strictly increasing.
179
+ otherwise the abs value of the maximum drawdown
180
+ of sequence X
181
+ """
182
+ cdef:
183
+ double mdd = 0
184
+ double peak = X[0]
185
+ double x, dd
186
+
187
+ for x in X:
188
+ if x > peak:
189
+ peak = x
190
+
191
+ dd = (peak - x) / peak
192
+
193
+ if dd > mdd:
194
+ mdd = dd
195
+
196
+ return mdd if mdd != 0.0 else 0.0
197
+
198
+
199
+ @cython.boundscheck(False)
200
+ @cython.wraparound(False)
201
+ def pivots_to_modes(int_t [:] pivots):
202
+ """
203
+ Translate pivots into trend modes.
204
+
205
+ :param pivots: the result of calling ``peak_valley_pivots``
206
+ :return: numpy array of trend modes. That is, between (VALLEY, PEAK] it
207
+ is 1 and between (PEAK, VALLEY] it is -1.
208
+ """
209
+
210
+ cdef:
211
+ int_t x, t
212
+ ndarray[int_t, ndim=1] modes = np.zeros(len(pivots),
213
+ dtype=np.int_)
214
+ int_t mode = -pivots[0]
215
+
216
+ modes[0] = pivots[0]
217
+
218
+ for t in range(1, len(pivots)):
219
+ x = pivots[t]
220
+ if x != 0:
221
+ modes[t] = mode
222
+ mode = -x
223
+ else:
224
+ modes[t] = mode
225
+
226
+ return modes
227
+
228
+
229
+ def compute_segment_returns(X, pivots):
230
+ """
231
+ :return: numpy array of the pivot-to-pivot returns for each segment."""
232
+ X = _to_ndarray(X)
233
+ pivot_points = X[pivots != 0]
234
+ return pivot_points[1:] / pivot_points[:-1] - 1.0
@@ -0,0 +1,18 @@
1
+ """
2
+ The package requirements do not enforce matplotlib as a requirement so this
3
+ package is optional. However, it's useful.
4
+ """
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+
8
+
9
+ def plot_pivots(X, pivots, ax=None):
10
+ if ax is None:
11
+ ax = plt.subplots(1, 1)[1]
12
+
13
+ ax.set_xlim(0, len(X))
14
+ ax.set_ylim(X.min()*0.99, X.max()*1.01)
15
+ ax.plot(np.arange(len(X)), X, 'k:', alpha=0.5)
16
+ ax.plot(np.arange(len(X))[pivots != 0], X[pivots != 0], 'k-')
17
+ ax.scatter(np.arange(len(X))[pivots == 1], X[pivots == 1], color='g')
18
+ ax.scatter(np.arange(len(X))[pivots == -1], X[pivots == -1], color='r')