fynance 2.1.0__py3-none-any.whl

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.
Files changed (139) hide show
  1. fynance/__init__.py +91 -0
  2. fynance/_exceptions.py +36 -0
  3. fynance/_wrappers.py +250 -0
  4. fynance/backtest/__init__.py +45 -0
  5. fynance/backtest/_basis_plot.py +93 -0
  6. fynance/backtest/backtest_neural_net.py +121 -0
  7. fynance/backtest/cost.py +61 -0
  8. fynance/backtest/dynamic_plot_backtest.py +536 -0
  9. fynance/backtest/engine.py +107 -0
  10. fynance/backtest/loss.py +117 -0
  11. fynance/backtest/plot.py +208 -0
  12. fynance/backtest/plot_backtest.py +211 -0
  13. fynance/backtest/plot_tools.py +219 -0
  14. fynance/backtest/print_stats.py +141 -0
  15. fynance/backtest/result.py +76 -0
  16. fynance/core/__init__.py +32 -0
  17. fynance/core/price_series.py +429 -0
  18. fynance/core/protocols.py +109 -0
  19. fynance/data/__init__.py +33 -0
  20. fynance/data/align.py +134 -0
  21. fynance/data/base.py +141 -0
  22. fynance/data/csv.py +52 -0
  23. fynance/data/parquet.py +36 -0
  24. fynance/data/split.py +99 -0
  25. fynance/estimator/__init__.py +13 -0
  26. fynance/estimator/estimator.py +109 -0
  27. fynance/features/__init__.py +58 -0
  28. fynance/features/_metrics_helpers.py +378 -0
  29. fynance/features/engineering.py +156 -0
  30. fynance/features/filters.py +439 -0
  31. fynance/features/indicators.py +830 -0
  32. fynance/features/momentums.py +629 -0
  33. fynance/features/money_management.py +66 -0
  34. fynance/features/regime.py +83 -0
  35. fynance/features/roll_functions.py +237 -0
  36. fynance/features/scale.py +545 -0
  37. fynance/features/stats.py +395 -0
  38. fynance/metrics/__init__.py +31 -0
  39. fynance/metrics/drawdown.py +260 -0
  40. fynance/metrics/ratios.py +617 -0
  41. fynance/metrics/returns.py +387 -0
  42. fynance/metrics/summary.py +79 -0
  43. fynance/models/__init__.py +108 -0
  44. fynance/models/_base.py +453 -0
  45. fynance/models/_recurrent_base.py +217 -0
  46. fynance/models/attention.py +174 -0
  47. fynance/models/cv_result.py +39 -0
  48. fynance/models/econometric_models.py +449 -0
  49. fynance/models/ensemble.py +109 -0
  50. fynance/models/gru.py +260 -0
  51. fynance/models/loss/__init__.py +41 -0
  52. fynance/models/loss/_base.py +53 -0
  53. fynance/models/loss/calmar.py +39 -0
  54. fynance/models/loss/directional.py +107 -0
  55. fynance/models/loss/hybrid.py +61 -0
  56. fynance/models/loss/omega.py +47 -0
  57. fynance/models/loss/sharpe.py +108 -0
  58. fynance/models/loss/sortino.py +92 -0
  59. fynance/models/lstm.py +373 -0
  60. fynance/models/mlp.py +186 -0
  61. fynance/models/rnn.py +115 -0
  62. fynance/models/rolling.py +557 -0
  63. fynance/models/tcn.py +179 -0
  64. fynance/models/training.py +101 -0
  65. fynance/models/transformer.py +190 -0
  66. fynance/plot/__init__.py +26 -0
  67. fynance/plot/_helpers.py +41 -0
  68. fynance/plot/equity.py +51 -0
  69. fynance/plot/returns.py +58 -0
  70. fynance/plot/tearsheet.py +82 -0
  71. fynance/portfolio/__init__.py +32 -0
  72. fynance/portfolio/allocation.py +801 -0
  73. fynance/portfolio/sizing.py +147 -0
  74. fynance/signal/__init__.py +24 -0
  75. fynance/signal/mappers.py +122 -0
  76. fynance/signal/pipeline.py +58 -0
  77. fynance/strategy/__init__.py +16 -0
  78. fynance/strategy/strategy.py +194 -0
  79. fynance/tests/__init__.py +0 -0
  80. fynance/tests/backtest/__init__.py +0 -0
  81. fynance/tests/backtest/test_backtest.py +103 -0
  82. fynance/tests/backtest/test_cost.py +36 -0
  83. fynance/tests/backtest/test_engine.py +71 -0
  84. fynance/tests/backtest/test_result.py +35 -0
  85. fynance/tests/core/__init__.py +0 -0
  86. fynance/tests/core/test_price_series.py +152 -0
  87. fynance/tests/core/test_protocols.py +68 -0
  88. fynance/tests/data/__init__.py +0 -0
  89. fynance/tests/data/test_align.py +46 -0
  90. fynance/tests/data/test_csv.py +40 -0
  91. fynance/tests/data/test_parquet.py +22 -0
  92. fynance/tests/data/test_registry.py +27 -0
  93. fynance/tests/data/test_split.py +45 -0
  94. fynance/tests/estimator/__init__.py +0 -0
  95. fynance/tests/estimator/test_estimator.py +75 -0
  96. fynance/tests/features/__init__.py +13 -0
  97. fynance/tests/features/test_engineering.py +58 -0
  98. fynance/tests/features/test_filters.py +141 -0
  99. fynance/tests/features/test_filters_benchmark.py +48 -0
  100. fynance/tests/features/test_indicators.py +145 -0
  101. fynance/tests/features/test_momentums.py +138 -0
  102. fynance/tests/features/test_momentums_numba.py +60 -0
  103. fynance/tests/features/test_property.py +130 -0
  104. fynance/tests/features/test_regime.py +35 -0
  105. fynance/tests/features/test_robustness.py +30 -0
  106. fynance/tests/features/test_roll_functions_numba.py +46 -0
  107. fynance/tests/features/test_scale.py +159 -0
  108. fynance/tests/features/test_technical_indicators.py +95 -0
  109. fynance/tests/metrics/__init__.py +0 -0
  110. fynance/tests/metrics/test_metrics.py +525 -0
  111. fynance/tests/metrics/test_metrics_numba.py +91 -0
  112. fynance/tests/metrics/test_summary.py +37 -0
  113. fynance/tests/models/__init__.py +13 -0
  114. fynance/tests/models/test_econometric_models.py +107 -0
  115. fynance/tests/models/test_econometric_numba_parity.py +61 -0
  116. fynance/tests/models/test_ensemble.py +74 -0
  117. fynance/tests/models/test_loss.py +212 -0
  118. fynance/tests/models/test_neural_network.py +520 -0
  119. fynance/tests/models/test_rnn.py +63 -0
  120. fynance/tests/models/test_rolling.py +129 -0
  121. fynance/tests/models/test_signalmodel.py +47 -0
  122. fynance/tests/models/test_tcn.py +96 -0
  123. fynance/tests/models/test_training.py +48 -0
  124. fynance/tests/models/test_transformer.py +106 -0
  125. fynance/tests/plot/__init__.py +0 -0
  126. fynance/tests/plot/test_smoke.py +55 -0
  127. fynance/tests/portfolio/__init__.py +0 -0
  128. fynance/tests/portfolio/test_allocation.py +249 -0
  129. fynance/tests/portfolio/test_sizing.py +49 -0
  130. fynance/tests/signal/__init__.py +0 -0
  131. fynance/tests/signal/test_signal.py +79 -0
  132. fynance/tests/strategy/__init__.py +0 -0
  133. fynance/tests/strategy/test_playground.py +49 -0
  134. fynance/tests/strategy/test_strategy.py +113 -0
  135. fynance-2.1.0.dist-info/METADATA +173 -0
  136. fynance-2.1.0.dist-info/RECORD +139 -0
  137. fynance-2.1.0.dist-info/WHEEL +5 -0
  138. fynance-2.1.0.dist-info/licenses/LICENSE.txt +20 -0
  139. fynance-2.1.0.dist-info/top_level.txt +1 -0
fynance/__init__.py ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # @Author: ArthurBernard
4
+ # @Email: arthur.bernard.92@gmail.com
5
+ # @Date: 2019-02-15 12:50:12
6
+ # @Last modified by: ArthurBernard
7
+ # @Last modified time: 2019-11-05 16:55:36
8
+
9
+ """
10
+ Fynance : A Python package for quant financial research
11
+ =======================================================
12
+
13
+ Documentation is available at
14
+ https://fynance.readthedocs.io/en/latest/index.html.
15
+
16
+ Contents
17
+ --------
18
+ Fynance is a python/cython project that includes several machine learning,
19
+ econometric and statistical subpackages specialy adapted for financial
20
+ analysis, portfolio allocation, and backtest trading strategies.
21
+
22
+ Subpackages
23
+ -----------
24
+ portfolio --- Portfolio allocation & sizing
25
+ backtest --- Backtest strategy tools
26
+ estimator --- Parameter estimation (Cython ARMA/GARCH)
27
+ features --- Features extraction
28
+ models --- Econometric and Neural Network models (using PyTorch)
29
+
30
+ Utility tools
31
+ -------------
32
+ _exceptions --- Fynance exceptions
33
+ tests --- Run fynance unittests
34
+ _wrappers --- Fynance wrapper functions
35
+
36
+ API stability policy (1.x series)
37
+ ---------------------------------
38
+ The symbols re-exported below from :mod:`fynance.models`,
39
+ :mod:`fynance.portfolio.allocation`, :mod:`fynance.features` and
40
+ :mod:`fynance.estimator` form the **public, stable API** for the 1.x
41
+ release line. Within 1.x:
42
+
43
+ - public function and class signatures are frozen — no removals, no
44
+ backward-incompatible signature changes;
45
+ - behavioural changes that would break user code go through one
46
+ release of :class:`DeprecationWarning` before becoming the new
47
+ default (see ``CONTRIBUTING.md``);
48
+ - :mod:`fynance.backtest` and :mod:`fynance.models` *internal* helpers
49
+ (names prefixed with ``_``) remain free to evolve.
50
+
51
+ Breaking changes are reserved for the 2.x line and tracked in
52
+ ``CHANGELOG.md``.
53
+
54
+ """
55
+
56
+ from importlib.metadata import PackageNotFoundError, version
57
+
58
+ try:
59
+ __version__ = version("fynance")
60
+ except PackageNotFoundError:
61
+ __version__ = "unknown"
62
+
63
+ __all__ = ['__version__']
64
+
65
+ import sys as _sys
66
+
67
+ from .backtest import *
68
+ from .core import *
69
+ from .data import *
70
+ from .estimator import *
71
+ from .features import *
72
+ from .metrics import *
73
+ from .models import *
74
+ from .plot import *
75
+ from .portfolio import *
76
+ from .signal import *
77
+ from .strategy import *
78
+
79
+ # Aggregate each subpackage's public surface. Use ``sys.modules`` rather than the
80
+ # package attributes, which a star import may have shadowed with a name that
81
+ # collides with a submodule (e.g. the ``backtest`` engine function).
82
+ for _name in ("core", "data", "models", "estimator", "features", "metrics",
83
+ "plot", "signal", "backtest", "portfolio", "strategy"):
84
+ __all__ += _sys.modules[f"{__name__}.{_name}"].__all__
85
+
86
+ # Restore the subpackage attribute shadowed by such a collision so that
87
+ # ``fynance.backtest`` resolves to the package (``fynance.backtest.backtest``
88
+ # stays the engine function).
89
+ backtest = _sys.modules[__name__ + ".backtest"]
90
+
91
+ del _sys, _name
fynance/_exceptions.py ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # @Author: ArthurBernard
4
+ # @Email: arthur.bernard.92@gmail.com
5
+ # @Date: 2019-10-23 17:01:36
6
+ # @Last modified by: ArthurBernard
7
+ # @Last modified time: 2019-10-23 17:17:32
8
+
9
+ """ Define some various richly-typed exceptions. """
10
+
11
+ # Built-in packages
12
+
13
+ # Third party packages
14
+
15
+ # Local packages
16
+
17
+
18
+ class ArraySizeError(ValueError, IndexError):
19
+ """ Size of the array was invalid. """
20
+
21
+ def __init__(self, size, axis=None, min_size=None, msg_prefix=None):
22
+ """ Initialize the array size error. """
23
+ msg = 'array of size {}'.format(size)
24
+
25
+ if axis is not None:
26
+ msg += ' in axis {}'.format(axis)
27
+
28
+ msg += ' is not allowed'
29
+
30
+ if min_size is not None:
31
+ msg += ', minimum size is {}'.format(min_size)
32
+
33
+ if msg_prefix is not None:
34
+ msg = '{}: {}'.format(msg_prefix, msg)
35
+
36
+ super().__init__(msg)
fynance/_wrappers.py ADDED
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # @Author: ArthurBernard
4
+ # @Email: arthur.bernard.92@gmail.com
5
+ # @Date: 2019-10-11 10:10:43
6
+ # @Last modified by: ArthurBernard
7
+ # @Last modified time: 2019-11-08 11:11:54
8
+
9
+ """ Some wrappers functions. """
10
+
11
+ # Built-in packages
12
+ from functools import wraps
13
+ from warnings import warn
14
+
15
+ # Third party packages
16
+ import numpy as np
17
+
18
+ # Local packages
19
+ from fynance._exceptions import ArraySizeError
20
+
21
+
22
+ def _check_dtype(X, dtype):
23
+ if dtype is None:
24
+ dtype = X.dtype
25
+
26
+ if X.dtype != np.float64:
27
+ X = X.astype(np.float64)
28
+
29
+ return X, dtype
30
+
31
+
32
+ def wrap_dtype(func):
33
+ """ Check the dtype of the `X` array.
34
+
35
+ Convert dtype of X to np.float64 before to pass to cython function and
36
+ convert to specified dtype at the end.
37
+
38
+ """
39
+ @wraps(func)
40
+ def check_dtype(X, *args, dtype=None, **kwargs):
41
+ X, dtype = _check_dtype(X, dtype)
42
+
43
+ if dtype != np.float64:
44
+ return func(X, *args, dtype=dtype, **kwargs).astype(dtype)
45
+
46
+ return func(X, *args, dtype=dtype, **kwargs)
47
+
48
+ return check_dtype
49
+
50
+
51
+ def wrap_axis(func):
52
+ """ Check if computation on `axis` of `X` array is available. """
53
+ @wraps(func)
54
+ def check_axis(X, *args, axis=0, min_size=0, **kwargs):
55
+ shape = X.shape
56
+ if X.ndim > 2:
57
+ warn(
58
+ 'currently, array of dimensions larger than 2 are not '
59
+ 'supported, it may lead to some issues',
60
+ category=UserWarning,
61
+ stacklevel=2,
62
+ )
63
+
64
+ if shape[axis] < min_size:
65
+
66
+ raise ArraySizeError(shape[axis], axis=axis, min_size=min_size)
67
+
68
+ elif X.ndim <= axis:
69
+
70
+ raise np.AxisError(axis, len(X.shape))
71
+
72
+ elif axis == 1 and X.ndim == 2:
73
+
74
+ return func(X.T, *args, axis=0, **kwargs).T
75
+
76
+ return func(X, *args, axis=axis, **kwargs)
77
+
78
+ return check_axis
79
+
80
+
81
+ def wrap_lags(func):
82
+ # Not clean, may lead to undesirable behavior
83
+ """ Check the max available lag for `X` array. """
84
+ @wraps(func)
85
+ def check_lags(X, k, *args, axis=0, **kwargs):
86
+ if k <= 0:
87
+ raise ValueError('lag {} must be greater than 0.'.format(k))
88
+
89
+ elif X.shape[axis] < k:
90
+ warn(
91
+ '{} lags is out of bounds for axis {} with size {}'.format(
92
+ k, axis, X.shape[axis]
93
+ ),
94
+ category=UserWarning,
95
+ stacklevel=2,
96
+ )
97
+ k = X.shape[axis]
98
+
99
+ return func(X, k, *args, axis=axis, **kwargs)
100
+
101
+ return check_lags
102
+
103
+
104
+ def wrap_window(func):
105
+ """ Check if the lagged window `w` is available for `X` array. """
106
+ @wraps(func)
107
+ def check_window(X, w=None, **kwargs):
108
+ if w == 0 or w is None:
109
+ w = X.shape[0]
110
+
111
+ elif 'min_size' in kwargs.keys() and w < kwargs['min_size']:
112
+
113
+ raise ValueError('lagged window of size {} is not available, \
114
+ must be greater than {}'.format(w, kwargs['min_size']))
115
+
116
+ elif w < 0:
117
+
118
+ raise ValueError('lagged window of size {} is not available, \
119
+ must be positive.'.format(w))
120
+
121
+ elif w > X.shape[0]:
122
+ warn(
123
+ 'lagged window of size {} is out of bounds with time axis '
124
+ 'of size {}'.format(w, X.shape[0]),
125
+ category=UserWarning,
126
+ stacklevel=2,
127
+ )
128
+ w = X.shape[0]
129
+
130
+ return func(X, w=int(w), **kwargs)
131
+
132
+ return check_window
133
+
134
+
135
+ def wrap_expo(func):
136
+ # Not clean, may lead to undesirable behavior
137
+ """ Check if parameters is allowed by the `kind` of moving avg/std. """
138
+ @wraps(func)
139
+ def check_expo(X, *args, w=None, kind=None, **kwargs):
140
+ if kind == 'e':
141
+ w = 1 - 2 / (1 + w)
142
+
143
+ return func(X, *args, w=w, kind=kind, **kwargs)
144
+
145
+ return check_expo
146
+
147
+
148
+ def wrap_null(func):
149
+ """ Check if there is any null value and raise an exception. """
150
+ @wraps(func)
151
+ def check_null(X, *args, **kwargs):
152
+ if (X == 0).any():
153
+
154
+ raise ValueError('null value in X is not allowed.')
155
+
156
+ return func(X, *args, **kwargs)
157
+
158
+ return check_null
159
+
160
+
161
+ def wrap_ddof(func):
162
+ """ Check if ddof is not greater than the number of timeframe. """
163
+ @wraps(func)
164
+ def check_ddof(X, *args, ddof=0, **kwargs):
165
+ if ddof >= X.shape[0]:
166
+ msg_prefix = 'with degree of freedom {}'.format(ddof)
167
+
168
+ raise ArraySizeError(X.shape[0], msg_prefix=msg_prefix)
169
+
170
+ elif ddof < 0:
171
+
172
+ raise ValueError('ddof must be a positive value')
173
+
174
+ return func(X, *args, ddof=ddof, **kwargs)
175
+
176
+ return check_ddof
177
+
178
+
179
+ def wrap_keepdims(func):
180
+ """ Check that output have same dimensions as input. """
181
+ @wraps(func)
182
+ def check_keepdims(X, *args, keepdims=False, **kwargs):
183
+ if keepdims:
184
+ out = func(X, *args, **kwargs)
185
+
186
+ return out.reshape(out.shape + (1,))
187
+
188
+ return func(X, *args, **kwargs)
189
+
190
+ return check_keepdims
191
+
192
+
193
+ class WrapperArray:
194
+ """ Object to wrap function that handle numpy arrays.
195
+
196
+ This object mix several wrapper functions.
197
+
198
+ Parameters
199
+ ----------
200
+ *args : {'dtype', 'axis', 'lags', 'window', 'null', 'ddof', 'keepdims'}
201
+ Wrapper functions.
202
+ **kwargs
203
+ Keyword arguments to pass to wrapper functions.
204
+
205
+ """
206
+
207
+ handler = {
208
+ 'dtype': wrap_dtype,
209
+ 'axis': wrap_axis,
210
+ 'lags': wrap_lags,
211
+ 'window': wrap_window,
212
+ 'null': wrap_null,
213
+ 'ddof': wrap_ddof,
214
+ }
215
+
216
+ def __init__(self, *args, **kwargs):
217
+ """ Initialize wrapper functions. """
218
+ self.wrappers = {key: self.handler[key] for key in args}
219
+ self.kw = kwargs
220
+
221
+ def __call__(self, func):
222
+ """ Wrap `func`.
223
+
224
+ Parameters
225
+ ----------
226
+ func : function
227
+ Function to wrap.
228
+
229
+ Returns
230
+ -------
231
+ function
232
+ Wrapped function.
233
+
234
+ """
235
+ @wraps(func)
236
+ def wrap(X, *args, **kwargs):
237
+ wrap_func = None
238
+ kwargs = {**kwargs, **self.kw}
239
+
240
+ for k, w in self.wrappers.items():
241
+
242
+ if wrap_func is None:
243
+ wrap_func = w(func)
244
+
245
+ else:
246
+ wrap_func = w(wrap_func)
247
+
248
+ return wrap_func(X, *args, **kwargs)
249
+
250
+ return wrap
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+
6
+ Some tools to backtest strategies
7
+
8
+ .. currentmodule:: fynance.backtest
9
+
10
+ .. toctree::
11
+ :maxdepth: 1
12
+ :caption: Contents:
13
+
14
+ backtest.tools
15
+ backtest.plot_object
16
+
17
+ """
18
+
19
+ from . import (
20
+ backtest_neural_net,
21
+ cost,
22
+ dynamic_plot_backtest,
23
+ engine,
24
+ plot_backtest,
25
+ plot_tools,
26
+ print_stats,
27
+ result,
28
+ )
29
+ from .backtest_neural_net import *
30
+ from .cost import *
31
+ from .dynamic_plot_backtest import *
32
+ from .engine import *
33
+ from .plot_backtest import *
34
+ from .plot_tools import *
35
+ from .print_stats import *
36
+ from .result import *
37
+
38
+ __all__ = print_stats.__all__
39
+ __all__ += cost.__all__
40
+ __all__ += engine.__all__
41
+ __all__ += result.__all__
42
+ __all__ += plot_tools.__all__
43
+ __all__ += plot_backtest.__all__
44
+ __all__ += dynamic_plot_backtest.__all__
45
+ __all__ += backtest_neural_net.__all__
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+ # @Author: ArthurBernard
4
+ # @Email: arthur.bernard.92@gmail.com
5
+ # @Date: 2020-10-24 09:03:49
6
+ # @Last modified by: ArthurBernard
7
+ # @Last modified time: 2020-11-20 09:09:02
8
+
9
+ """ Description. """
10
+
11
+ # Built-in packages
12
+ from abc import ABCMeta
13
+
14
+ # Third party packages
15
+ # import numpy as np
16
+ import matplotlib.pyplot as plt
17
+
18
+ # import seaborn as sns
19
+
20
+ # Local packages
21
+
22
+ __all__ = []
23
+
24
+
25
+ class _BasisAxes(metaclass=ABCMeta):
26
+ def __call__(self, fig, n_rows, n_cols, n_axes):
27
+ self.fig = fig
28
+ self.n_axes = n_axes
29
+ self.ax = self.fig.add_subplot(n_rows, n_cols, n_axes)
30
+
31
+ # @abstractmethod
32
+ def plot(self, y, x=None):
33
+ if x is None:
34
+ self.ax.plot(y)
35
+
36
+ else:
37
+ self.ax.plot(x, y)
38
+
39
+
40
+ class _BasisPlot:
41
+ def __init__(self, **kwargs):
42
+ self.fig = plt.figure(**kwargs)
43
+ self._n_axes = 0
44
+ self._n_cols = 1
45
+ self._n_rows = 0
46
+ self.axes = {}
47
+ self.keys = []
48
+
49
+ def __setitem__(self, key, value: _BasisAxes):
50
+ # Set optimal number of axes on figure plot
51
+ self._n_axes += 1
52
+ sqrt_n_axes = self._n_axes ** 0.5
53
+ if self._n_cols * self._n_rows < self._n_axes:
54
+ if self._n_rows > self._n_cols and self._n_rows >= sqrt_n_axes:
55
+ self._n_cols += 1
56
+
57
+ else:
58
+ self._n_rows += 1
59
+
60
+ print(self._n_rows, self._n_cols)
61
+ self.axes[key] = value
62
+ self.keys.append(key)
63
+
64
+ def __delitem__(self, key):
65
+ # Set optimal number of axes on figure plot
66
+ self._n_axes -= 1
67
+ n_cols = self._n_cols
68
+ n_rows = self._n_rows
69
+ if (n_cols - 1) * n_rows >= self._n_axes:
70
+ self._n_cols -= 1
71
+
72
+ elif (n_rows - 1) * n_cols >= self._n_axes:
73
+ self._n_rows -= 1
74
+
75
+ if self._n_axes < 1:
76
+ self.fig = None
77
+ print("destruct fig")
78
+
79
+ del self.axes[key]
80
+ self.keys.remove(key)
81
+
82
+ def __getitem__(self, key):
83
+ return self.axes[key]
84
+
85
+ def set_axes(self):
86
+ for i, key in enumerate(self.keys, 1):
87
+ self.axes[key](self.fig, self._n_rows, self._n_cols, i)
88
+
89
+ return self
90
+
91
+
92
+ if __name__ == "__main__":
93
+ pass
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+
4
+ """ Live multi-panel backtest figure for neural-network training.
5
+
6
+ Provides :class:`BacktestNeuralNet`, which composes the dynamic
7
+ loss / accuracy / performance plots from
8
+ :mod:`fynance.backtest.dynamic_plot_backtest` into a single updating figure.
9
+ """
10
+
11
+ # Third-party packages
12
+ from matplotlib import pyplot as plt
13
+
14
+ # Local packages
15
+ from fynance.backtest.dynamic_plot_backtest import (
16
+ DynaPlotAccuracy,
17
+ DynaPlotLoss,
18
+ DynaPlotPerf,
19
+ )
20
+
21
+ __all__ = ['BacktestNeuralNet']
22
+
23
+
24
+ class BacktestNeuralNet:
25
+
26
+ def __init__(self, figsize=(9, 6), loss_xlim=None, perf_xlim=None,
27
+ accu_xlim=None, plot_accuracy=False, plot_loss=True,
28
+ plot_perf=False, **subplot_kw):
29
+ # Set dynamic plot object
30
+ n_rows = plot_accuracy + plot_loss + plot_perf
31
+ self.f, self.axes = plt.subplots(n_rows, 1, figsize=figsize,
32
+ **subplot_kw)
33
+
34
+ if n_rows == 1:
35
+ self.axes = [self.axes]
36
+
37
+ plt.ion()
38
+ self.accu_is_plot = False
39
+ self.loss_is_plot = False
40
+ self.perf_is_plot = False
41
+
42
+ if plot_accuracy:
43
+ self.set_plot_accuracy(self.axes[0], accu_xlim=accu_xlim)
44
+
45
+ if plot_loss:
46
+ self.set_plot_loss(self.axes[int(plot_accuracy)],
47
+ loss_xlim=loss_xlim)
48
+
49
+ if plot_perf:
50
+ self.set_plot_perf(self.axes[int(plot_accuracy + plot_loss)],
51
+ perf_xlim=perf_xlim)
52
+
53
+ def set_plot_accuracy(self, ax, accu_xlim=None):
54
+ """ Set plot accuracy object. """
55
+ self.dp_accu = DynaPlotAccuracy(self.f, ax)
56
+ self.dp_accu.ax.grid()
57
+ self.dp_accu.ax.set_autoscaley_on(True)
58
+
59
+ if accu_xlim is not None:
60
+ self.dp_accu.ax.set_xlim(*accu_xlim, auto=False)
61
+ print("setup xlim:", accu_xlim)
62
+ self.dp_accu.ax.set_autoscalex_on(False)
63
+
64
+ else:
65
+ self.dp_accu.ax.set_autoscalex_on(True)
66
+
67
+ def plot_accuracy(self, test, eval, train=None, clear=True):
68
+ """ Plot accuracy scores for test and evaluate set. """
69
+ self.dp_accu.plot(test=test, eval=eval, train=train, clear=clear)
70
+ self.accu_is_plot = True
71
+
72
+ def update_accuracy(self, test, eval, train=None):
73
+ """ Plot accuracy scores for test and evaluate set. """
74
+ self.dp_accu.update(test=test, eval=eval, train=train)
75
+
76
+ def set_plot_loss(self, ax, loss_xlim=None):
77
+ """ Set plot loss object. """
78
+ self.dp_loss = DynaPlotLoss(self.f, ax)
79
+ self.dp_loss.ax.grid()
80
+ self.dp_loss.ax.set_autoscaley_on(True)
81
+
82
+ if loss_xlim is not None:
83
+ self.dp_loss.ax.set_xlim(*loss_xlim, auto=False)
84
+ print("setup xlim:", loss_xlim)
85
+ self.dp_loss.ax.set_autoscalex_on(False)
86
+
87
+ else:
88
+ self.dp_loss.ax.set_autoscalex_on(True)
89
+
90
+ def plot_loss(self, test, eval, train=None, clear=True):
91
+ """ Plot loss function values for test and evaluate set. """
92
+ self.dp_loss.plot(test=test, eval=eval, train=train, clear=clear)
93
+ self.loss_is_plot = True
94
+
95
+ def update_loss(self, test, eval, train=None):
96
+ """ Plot loss function values for test and evaluate set. """
97
+ self.dp_loss.update(test=test, eval=eval, train=train)
98
+
99
+ def set_plot_perf(self, ax, perf_xlim=None):
100
+ # set perf plot
101
+ self.dp_perf = DynaPlotPerf(self.f, ax)
102
+ self.dp_perf.ax.grid()
103
+ self.dp_perf.ax.set_autoscaley_on(True)
104
+ if perf_xlim is not None:
105
+ self.dp_perf.ax.set_xlim(*perf_xlim, auto=False)
106
+ print("setup xlim:", perf_xlim)
107
+ self.dp_perf.ax.set_autoscalex_on(False)
108
+
109
+ else:
110
+ self.dp_perf.ax.set_autoscalex_on(True)
111
+
112
+ def plot_perf(self, test, eval, underlying=None, index=None, clear=True):
113
+ """ Plot performance values for test and eval set. """
114
+ self.dp_perf.plot(test=test, eval=eval, underlying=underlying,
115
+ index=index, clear=clear)
116
+ self.perf_is_plot = True
117
+
118
+ def update_perf(self, test, eval, underlying=None, index=None, clear=True):
119
+ """ Update performance values for test and eval set. """
120
+ self.dp_perf.update(test=test, eval=eval, underlying=underlying,
121
+ index=index, clear=clear)
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ # coding: utf-8
3
+
4
+ """ Transaction cost models for the vectorized backtest engine.
5
+
6
+ Concretizes the :class:`~fynance.core.protocols.CostModel` seam. Only the
7
+ proportional (turnover-based) model ships in 2.0; non-linear slippage is a
8
+ documented extension point.
9
+
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ # Third-party packages
15
+ import numpy as np
16
+ from numpy.typing import NDArray
17
+
18
+ # Local packages
19
+ from fynance.portfolio.sizing import transaction_cost
20
+
21
+ __all__ = ['ProportionalCost']
22
+
23
+
24
+ class ProportionalCost:
25
+ """ Proportional transaction cost: ``(fee + slippage) * turnover``.
26
+
27
+ Turnover at each step is the absolute traded weight
28
+ :math:`\\sum_i |w_{t,i} - w_{t-1,i}|` (the first step charges the initial
29
+ position). Conforms to :class:`~fynance.core.protocols.CostModel`.
30
+
31
+ Parameters
32
+ ----------
33
+ fee : float
34
+ Proportional fee per unit traded (e.g. ``0.001`` = 10 bps).
35
+ slippage : float
36
+ Additional proportional slippage per unit traded.
37
+
38
+ Examples
39
+ --------
40
+ >>> import numpy as np
41
+ >>> cost = ProportionalCost(fee=0.01)
42
+ >>> cost(np.array([[1.0, 0.0], [0.5, 0.5], [0.5, 0.5]]))
43
+ array([0.01, 0.01, 0. ])
44
+
45
+ """
46
+
47
+ def __init__(self, fee: float = 0.0, slippage: float = 0.0):
48
+ """ Store the cost rates. """
49
+ self.fee = fee
50
+ self.slippage = slippage
51
+
52
+ def __call__(self, weights: NDArray) -> NDArray[np.float64]:
53
+ """ Return the per-step proportional cost of a weight book. """
54
+ rate = self.fee + self.slippage
55
+
56
+ if rate == 0.0:
57
+ w = np.asarray(weights, dtype=np.float64)
58
+
59
+ return np.zeros(w.shape[0], dtype=np.float64)
60
+
61
+ return transaction_cost(weights, fee=rate)