openbacktesting 0.2.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.
- openbacktesting/__init__.py +85 -0
- openbacktesting/aggregation.py +8 -0
- openbacktesting/broker/__init__.py +54 -0
- openbacktesting/broker/commission.py +57 -0
- openbacktesting/broker/core.py +1122 -0
- openbacktesting/broker/execution.py +62 -0
- openbacktesting/broker/instrument.py +106 -0
- openbacktesting/broker/ledger.py +60 -0
- openbacktesting/broker/models.py +236 -0
- openbacktesting/broker/observations.py +99 -0
- openbacktesting/broker/requests.py +192 -0
- openbacktesting/broker/sizing.py +76 -0
- openbacktesting/colors.py +175 -0
- openbacktesting/data/__init__.py +20 -0
- openbacktesting/data/aggregation.py +39 -0
- openbacktesting/data/context.py +30 -0
- openbacktesting/data/core.py +218 -0
- openbacktesting/data/generators.py +54 -0
- openbacktesting/data/strategy_data.py +103 -0
- openbacktesting/data/timeframe.py +69 -0
- openbacktesting/engine.py +34 -0
- openbacktesting/helpers.py +77 -0
- openbacktesting/indicator_api.py +10 -0
- openbacktesting/indicator_methods.py +123 -0
- openbacktesting/indicator_types.py +19 -0
- openbacktesting/indicators/__init__.py +7 -0
- openbacktesting/indicators/api.py +97 -0
- openbacktesting/indicators/calculus.py +75 -0
- openbacktesting/indicators/candles/__init__.py +0 -0
- openbacktesting/indicators/candles/cdl_doji.py +17 -0
- openbacktesting/indicators/candles/cdl_engulfing.py +14 -0
- openbacktesting/indicators/candles/cdl_eveningstar.py +15 -0
- openbacktesting/indicators/candles/cdl_hammer.py +14 -0
- openbacktesting/indicators/candles/cdl_harami.py +14 -0
- openbacktesting/indicators/candles/cdl_inside.py +14 -0
- openbacktesting/indicators/candles/cdl_marubozu.py +14 -0
- openbacktesting/indicators/candles/cdl_morningstar.py +15 -0
- openbacktesting/indicators/candles/cdl_shootingstar.py +14 -0
- openbacktesting/indicators/candles/cdl_z.py +28 -0
- openbacktesting/indicators/candles/ha.py +19 -0
- openbacktesting/indicators/catalog.json +8348 -0
- openbacktesting/indicators/common.py +94 -0
- openbacktesting/indicators/cycles/__init__.py +0 -0
- openbacktesting/indicators/cycles/dsp.py +10 -0
- openbacktesting/indicators/cycles/ebsw.py +10 -0
- openbacktesting/indicators/cycles/ht_dcperiod.py +10 -0
- openbacktesting/indicators/cycles/ht_dcphase.py +10 -0
- openbacktesting/indicators/cycles/ht_phasor.py +17 -0
- openbacktesting/indicators/cycles/ht_sine.py +17 -0
- openbacktesting/indicators/cycles/ht_trendmode.py +10 -0
- openbacktesting/indicators/cycles/msw.py +19 -0
- openbacktesting/indicators/methods.py +5762 -0
- openbacktesting/indicators/momentum/__init__.py +0 -0
- openbacktesting/indicators/momentum/ac.py +9 -0
- openbacktesting/indicators/momentum/ao.py +10 -0
- openbacktesting/indicators/momentum/apo.py +10 -0
- openbacktesting/indicators/momentum/bias.py +10 -0
- openbacktesting/indicators/momentum/bop.py +10 -0
- openbacktesting/indicators/momentum/brar.py +26 -0
- openbacktesting/indicators/momentum/cci.py +10 -0
- openbacktesting/indicators/momentum/cfo.py +10 -0
- openbacktesting/indicators/momentum/cg.py +10 -0
- openbacktesting/indicators/momentum/cmo.py +10 -0
- openbacktesting/indicators/momentum/coppock.py +10 -0
- openbacktesting/indicators/momentum/crsi.py +28 -0
- openbacktesting/indicators/momentum/cti.py +10 -0
- openbacktesting/indicators/momentum/dm.py +17 -0
- openbacktesting/indicators/momentum/er.py +10 -0
- openbacktesting/indicators/momentum/eri.py +17 -0
- openbacktesting/indicators/momentum/fisher.py +17 -0
- openbacktesting/indicators/momentum/fosc.py +10 -0
- openbacktesting/indicators/momentum/gator.py +13 -0
- openbacktesting/indicators/momentum/ibs.py +7 -0
- openbacktesting/indicators/momentum/inertia.py +22 -0
- openbacktesting/indicators/momentum/kdj.py +18 -0
- openbacktesting/indicators/momentum/kst.py +30 -0
- openbacktesting/indicators/momentum/lrsi.py +10 -0
- openbacktesting/indicators/momentum/macd.py +27 -0
- openbacktesting/indicators/momentum/macdext.py +27 -0
- openbacktesting/indicators/momentum/macdfix.py +18 -0
- openbacktesting/indicators/momentum/mom.py +10 -0
- openbacktesting/indicators/momentum/pgo.py +10 -0
- openbacktesting/indicators/momentum/pmo.py +13 -0
- openbacktesting/indicators/momentum/po.py +10 -0
- openbacktesting/indicators/momentum/ppo.py +20 -0
- openbacktesting/indicators/momentum/psl.py +12 -0
- openbacktesting/indicators/momentum/pvo.py +20 -0
- openbacktesting/indicators/momentum/qqe.py +31 -0
- openbacktesting/indicators/momentum/rci.py +20 -0
- openbacktesting/indicators/momentum/rmi.py +7 -0
- openbacktesting/indicators/momentum/roc.py +10 -0
- openbacktesting/indicators/momentum/rocp.py +10 -0
- openbacktesting/indicators/momentum/rocr.py +10 -0
- openbacktesting/indicators/momentum/rocr100.py +10 -0
- openbacktesting/indicators/momentum/rsi.py +10 -0
- openbacktesting/indicators/momentum/rsx.py +10 -0
- openbacktesting/indicators/momentum/rvgi.py +26 -0
- openbacktesting/indicators/momentum/slope.py +17 -0
- openbacktesting/indicators/momentum/smc_sweep.py +18 -0
- openbacktesting/indicators/momentum/smi.py +21 -0
- openbacktesting/indicators/momentum/smi_stochastic.py +15 -0
- openbacktesting/indicators/momentum/squeeze.py +47 -0
- openbacktesting/indicators/momentum/squeeze_pro.py +53 -0
- openbacktesting/indicators/momentum/stc.py +20 -0
- openbacktesting/indicators/momentum/stoch.py +19 -0
- openbacktesting/indicators/momentum/stochf.py +19 -0
- openbacktesting/indicators/momentum/stochrsi.py +19 -0
- openbacktesting/indicators/momentum/td_seq.py +17 -0
- openbacktesting/indicators/momentum/trix.py +19 -0
- openbacktesting/indicators/momentum/trixh.py +20 -0
- openbacktesting/indicators/momentum/tsi.py +26 -0
- openbacktesting/indicators/momentum/uo.py +22 -0
- openbacktesting/indicators/momentum/vwmacd.py +20 -0
- openbacktesting/indicators/momentum/wavetrend.py +15 -0
- openbacktesting/indicators/momentum/willr.py +10 -0
- openbacktesting/indicators/overlap/__init__.py +0 -0
- openbacktesting/indicators/overlap/alligator.py +13 -0
- openbacktesting/indicators/overlap/alma.py +33 -0
- openbacktesting/indicators/overlap/anchored_vwap.py +50 -0
- openbacktesting/indicators/overlap/anchored_vwap_bands.py +13 -0
- openbacktesting/indicators/overlap/avgprice.py +10 -0
- openbacktesting/indicators/overlap/dema.py +10 -0
- openbacktesting/indicators/overlap/ema.py +12 -0
- openbacktesting/indicators/overlap/frama.py +29 -0
- openbacktesting/indicators/overlap/fwma.py +10 -0
- openbacktesting/indicators/overlap/hilo.py +26 -0
- openbacktesting/indicators/overlap/hl2.py +10 -0
- openbacktesting/indicators/overlap/hlc3.py +10 -0
- openbacktesting/indicators/overlap/hma.py +10 -0
- openbacktesting/indicators/overlap/ht_trendline.py +10 -0
- openbacktesting/indicators/overlap/hwma.py +10 -0
- openbacktesting/indicators/overlap/ichimoku.py +44 -0
- openbacktesting/indicators/overlap/jma.py +10 -0
- openbacktesting/indicators/overlap/kama.py +10 -0
- openbacktesting/indicators/overlap/linreg.py +15 -0
- openbacktesting/indicators/overlap/linregangle.py +10 -0
- openbacktesting/indicators/overlap/linregintercept.py +10 -0
- openbacktesting/indicators/overlap/linregslope.py +10 -0
- openbacktesting/indicators/overlap/ma.py +10 -0
- openbacktesting/indicators/overlap/ma_envelopes.py +16 -0
- openbacktesting/indicators/overlap/mama.py +17 -0
- openbacktesting/indicators/overlap/mavp.py +17 -0
- openbacktesting/indicators/overlap/mcgd.py +10 -0
- openbacktesting/indicators/overlap/medprice.py +10 -0
- openbacktesting/indicators/overlap/midpoint.py +10 -0
- openbacktesting/indicators/overlap/midprice.py +10 -0
- openbacktesting/indicators/overlap/mmar.py +21 -0
- openbacktesting/indicators/overlap/ohlc4.py +10 -0
- openbacktesting/indicators/overlap/pwma.py +10 -0
- openbacktesting/indicators/overlap/rainbow.py +25 -0
- openbacktesting/indicators/overlap/rma.py +10 -0
- openbacktesting/indicators/overlap/sinwma.py +10 -0
- openbacktesting/indicators/overlap/sma.py +14 -0
- openbacktesting/indicators/overlap/ssf.py +10 -0
- openbacktesting/indicators/overlap/supertrend.py +21 -0
- openbacktesting/indicators/overlap/swma.py +10 -0
- openbacktesting/indicators/overlap/t3.py +10 -0
- openbacktesting/indicators/overlap/tema.py +10 -0
- openbacktesting/indicators/overlap/trima.py +10 -0
- openbacktesting/indicators/overlap/tsf.py +10 -0
- openbacktesting/indicators/overlap/typprice.py +10 -0
- openbacktesting/indicators/overlap/vidya.py +10 -0
- openbacktesting/indicators/overlap/vwap.py +10 -0
- openbacktesting/indicators/overlap/vwma.py +10 -0
- openbacktesting/indicators/overlap/wcp.py +10 -0
- openbacktesting/indicators/overlap/wma.py +10 -0
- openbacktesting/indicators/overlap/zlma.py +10 -0
- openbacktesting/indicators/performance/__init__.py +0 -0
- openbacktesting/indicators/performance/drawdown.py +18 -0
- openbacktesting/indicators/performance/log_return.py +10 -0
- openbacktesting/indicators/performance/percent_return.py +10 -0
- openbacktesting/indicators/predicates.py +88 -0
- openbacktesting/indicators/registry.py +571 -0
- openbacktesting/indicators/runtime.py +290 -0
- openbacktesting/indicators/statistics/__init__.py +0 -0
- openbacktesting/indicators/statistics/beta.py +10 -0
- openbacktesting/indicators/statistics/correl.py +10 -0
- openbacktesting/indicators/statistics/entropy.py +10 -0
- openbacktesting/indicators/statistics/kurtosis.py +10 -0
- openbacktesting/indicators/statistics/mad.py +10 -0
- openbacktesting/indicators/statistics/md.py +10 -0
- openbacktesting/indicators/statistics/median.py +10 -0
- openbacktesting/indicators/statistics/mode.py +48 -0
- openbacktesting/indicators/statistics/quantile.py +10 -0
- openbacktesting/indicators/statistics/skew.py +10 -0
- openbacktesting/indicators/statistics/stderr.py +10 -0
- openbacktesting/indicators/statistics/stdev.py +10 -0
- openbacktesting/indicators/statistics/variance.py +10 -0
- openbacktesting/indicators/statistics/zscore.py +10 -0
- openbacktesting/indicators/trend/__init__.py +0 -0
- openbacktesting/indicators/trend/adx.py +28 -0
- openbacktesting/indicators/trend/adxr.py +28 -0
- openbacktesting/indicators/trend/amat.py +19 -0
- openbacktesting/indicators/trend/aroon.py +18 -0
- openbacktesting/indicators/trend/atr_stop.py +33 -0
- openbacktesting/indicators/trend/chop.py +20 -0
- openbacktesting/indicators/trend/cksp.py +17 -0
- openbacktesting/indicators/trend/cpr.py +57 -0
- openbacktesting/indicators/trend/decay.py +10 -0
- openbacktesting/indicators/trend/decreasing.py +18 -0
- openbacktesting/indicators/trend/dpo.py +12 -0
- openbacktesting/indicators/trend/dx.py +19 -0
- openbacktesting/indicators/trend/edecay.py +10 -0
- openbacktesting/indicators/trend/increasing.py +18 -0
- openbacktesting/indicators/trend/long_run.py +10 -0
- openbacktesting/indicators/trend/minus_dm.py +10 -0
- openbacktesting/indicators/trend/pivot_points.py +89 -0
- openbacktesting/indicators/trend/pivothigh.py +23 -0
- openbacktesting/indicators/trend/pivotlow.py +23 -0
- openbacktesting/indicators/trend/plus_dm.py +10 -0
- openbacktesting/indicators/trend/pmax.py +18 -0
- openbacktesting/indicators/trend/psar.py +19 -0
- openbacktesting/indicators/trend/qstick.py +10 -0
- openbacktesting/indicators/trend/sarext.py +35 -0
- openbacktesting/indicators/trend/short_run.py +10 -0
- openbacktesting/indicators/trend/tsignals.py +26 -0
- openbacktesting/indicators/trend/ttm_trend.py +16 -0
- openbacktesting/indicators/trend/vhf.py +10 -0
- openbacktesting/indicators/trend/volatility_stop.py +33 -0
- openbacktesting/indicators/trend/vortex.py +17 -0
- openbacktesting/indicators/trend/xsignals.py +29 -0
- openbacktesting/indicators/types.py +344 -0
- openbacktesting/indicators/utility/__init__.py +0 -0
- openbacktesting/indicators/utility/barssince.py +21 -0
- openbacktesting/indicators/utility/change.py +7 -0
- openbacktesting/indicators/utility/cross.py +12 -0
- openbacktesting/indicators/utility/crossover.py +11 -0
- openbacktesting/indicators/utility/crossunder.py +11 -0
- openbacktesting/indicators/utility/cum.py +7 -0
- openbacktesting/indicators/utility/falling.py +10 -0
- openbacktesting/indicators/utility/highest.py +7 -0
- openbacktesting/indicators/utility/highestbars.py +29 -0
- openbacktesting/indicators/utility/lowest.py +7 -0
- openbacktesting/indicators/utility/lowestbars.py +29 -0
- openbacktesting/indicators/utility/percentile_linear_interpolation.py +12 -0
- openbacktesting/indicators/utility/percentile_nearest_rank.py +16 -0
- openbacktesting/indicators/utility/percentrank.py +7 -0
- openbacktesting/indicators/utility/range.py +8 -0
- openbacktesting/indicators/utility/rising.py +10 -0
- openbacktesting/indicators/utility/rolling_count.py +12 -0
- openbacktesting/indicators/utility/rolling_sum.py +7 -0
- openbacktesting/indicators/utility/running_max.py +7 -0
- openbacktesting/indicators/utility/running_min.py +7 -0
- openbacktesting/indicators/utility/shift.py +7 -0
- openbacktesting/indicators/utility/valuewhen.py +25 -0
- openbacktesting/indicators/volatility/__init__.py +0 -0
- openbacktesting/indicators/volatility/aberration.py +21 -0
- openbacktesting/indicators/volatility/accbands.py +20 -0
- openbacktesting/indicators/volatility/adr.py +7 -0
- openbacktesting/indicators/volatility/atr.py +12 -0
- openbacktesting/indicators/volatility/avolume.py +10 -0
- openbacktesting/indicators/volatility/bbands.py +20 -0
- openbacktesting/indicators/volatility/ce.py +25 -0
- openbacktesting/indicators/volatility/cvi.py +10 -0
- openbacktesting/indicators/volatility/donchian.py +20 -0
- openbacktesting/indicators/volatility/hvol.py +10 -0
- openbacktesting/indicators/volatility/hwc.py +27 -0
- openbacktesting/indicators/volatility/kc.py +27 -0
- openbacktesting/indicators/volatility/massi.py +10 -0
- openbacktesting/indicators/volatility/natr.py +19 -0
- openbacktesting/indicators/volatility/pdist.py +10 -0
- openbacktesting/indicators/volatility/rvi.py +21 -0
- openbacktesting/indicators/volatility/starc.py +11 -0
- openbacktesting/indicators/volatility/thermo.py +28 -0
- openbacktesting/indicators/volatility/true_range.py +31 -0
- openbacktesting/indicators/volatility/ui.py +10 -0
- openbacktesting/indicators/volume/__init__.py +0 -0
- openbacktesting/indicators/volume/ad.py +10 -0
- openbacktesting/indicators/volume/adosc.py +19 -0
- openbacktesting/indicators/volume/aobv.py +31 -0
- openbacktesting/indicators/volume/cmf.py +12 -0
- openbacktesting/indicators/volume/efi.py +12 -0
- openbacktesting/indicators/volume/emv.py +10 -0
- openbacktesting/indicators/volume/eom.py +19 -0
- openbacktesting/indicators/volume/iii.py +9 -0
- openbacktesting/indicators/volume/kvo.py +28 -0
- openbacktesting/indicators/volume/marketfi.py +10 -0
- openbacktesting/indicators/volume/mfi.py +12 -0
- openbacktesting/indicators/volume/nvi.py +26 -0
- openbacktesting/indicators/volume/obv.py +10 -0
- openbacktesting/indicators/volume/pvi.py +26 -0
- openbacktesting/indicators/volume/pvol.py +10 -0
- openbacktesting/indicators/volume/pvr.py +10 -0
- openbacktesting/indicators/volume/pvt.py +10 -0
- openbacktesting/indicators/volume/rvol.py +8 -0
- openbacktesting/indicators/volume/vfi.py +18 -0
- openbacktesting/indicators/volume/vosc.py +10 -0
- openbacktesting/indicators/volume/wad.py +10 -0
- openbacktesting/indicators/volume/wvad.py +9 -0
- openbacktesting/inputs.py +51 -0
- openbacktesting/lib.py +31 -0
- openbacktesting/logs.py +129 -0
- openbacktesting/optimization.py +8 -0
- openbacktesting/optimizer/__init__.py +25 -0
- openbacktesting/optimizer/_semantics.py +155 -0
- openbacktesting/optimizer/identity.py +45 -0
- openbacktesting/optimizer/models.py +92 -0
- openbacktesting/optimizer/plan.py +129 -0
- openbacktesting/optimizer/portfolio-result.schema.json +4984 -0
- openbacktesting/optimizer/query.py +257 -0
- openbacktesting/optimizer/runner.py +98 -0
- openbacktesting/optimizer/storage.py +261 -0
- openbacktesting/optimizer/validation.py +64 -0
- openbacktesting/parallel.py +9 -0
- openbacktesting/plotting/__init__.py +23 -0
- openbacktesting/plotting/collector.py +509 -0
- openbacktesting/plotting/encoding.py +102 -0
- openbacktesting/plotting/types.py +92 -0
- openbacktesting/portfolio/__init__.py +15 -0
- openbacktesting/portfolio/analysis.py +245 -0
- openbacktesting/portfolio/builder.py +381 -0
- openbacktesting/portfolio/catalog.json +1404 -0
- openbacktesting/portfolio/helpers.py +92 -0
- openbacktesting/portfolio/metrics.py +186 -0
- openbacktesting/portfolio/models.py +63 -0
- openbacktesting/portfolio/periods.py +164 -0
- openbacktesting/portfolio/rates.py +14 -0
- openbacktesting/portfolio/snapshot.py +111 -0
- openbacktesting/portfolio/stats.py +272 -0
- openbacktesting/portfolio/trades.py +136 -0
- openbacktesting/portfolio/trading.py +215 -0
- openbacktesting/py.typed +0 -0
- openbacktesting/runtime.py +330 -0
- openbacktesting/simulation/__init__.py +17 -0
- openbacktesting/simulation/collection.py +13 -0
- openbacktesting/simulation/config.py +241 -0
- openbacktesting/simulation/core.py +528 -0
- openbacktesting/simulation/optimization.py +326 -0
- openbacktesting/simulation/parallel.py +145 -0
- openbacktesting/simulation/variants.py +107 -0
- openbacktesting/strategy/__init__.py +19 -0
- openbacktesting/strategy/bar_state.py +14 -0
- openbacktesting/strategy/base.py +810 -0
- openbacktesting/strategy/declaration.py +90 -0
- openbacktesting/strategy/helpers.py +198 -0
- openbacktesting/strategy/inputs.py +460 -0
- openbacktesting/strategy/sizing.py +80 -0
- openbacktesting/strategy/templates.py +160 -0
- openbacktesting/strategy_data.py +10 -0
- openbacktesting/test/BTCUSD.csv +157 -0
- openbacktesting/test/EURUSD.csv +5001 -0
- openbacktesting/test/GOOG.csv +2149 -0
- openbacktesting/test/__init__.py +29 -0
- openbacktesting/test/__main__.py +11 -0
- openbacktesting/test/_test.py +1312 -0
- openbacktesting/util.py +31 -0
- openbacktesting-0.2.0.dist-info/METADATA +151 -0
- openbacktesting-0.2.0.dist-info/RECORD +350 -0
- openbacktesting-0.2.0.dist-info/WHEEL +4 -0
- openbacktesting-0.2.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""Provider-independent indicator definitions, immutable results, and cursor views."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from math import isfinite
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from numpy.lib.mixins import NDArrayOperatorsMixin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class IndicatorError(ValueError):
|
|
15
|
+
"""Stable error categories; detail is not a programmatic dispatch interface."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, code, indicator, detail, *, backend=None, parameter=None):
|
|
18
|
+
self.code = code
|
|
19
|
+
self.indicator = indicator
|
|
20
|
+
self.backend = backend
|
|
21
|
+
self.parameter = parameter
|
|
22
|
+
super().__init__(f"{code}: {indicator}: {detail}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def immutable(values, dtype=None):
|
|
26
|
+
"""Backed by immutable bytes, so setflags cannot restore write access."""
|
|
27
|
+
array = np.asarray(values, dtype=dtype)
|
|
28
|
+
if array.dtype.hasobject:
|
|
29
|
+
raise ValueError("Indicator arrays do not accept object dtype")
|
|
30
|
+
return np.frombuffer(array.tobytes(), dtype=array.dtype).reshape(array.shape)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class IndicatorCursor:
|
|
35
|
+
length: int
|
|
36
|
+
phase: str = "init"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IndicatorHandle(NDArrayOperatorsMixin):
|
|
40
|
+
"""Time is the final dimension; all derived views apply cursors and selectors only when read."""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
values,
|
|
45
|
+
*,
|
|
46
|
+
name,
|
|
47
|
+
index,
|
|
48
|
+
cursor=None,
|
|
49
|
+
valid=None,
|
|
50
|
+
required=None,
|
|
51
|
+
metadata=None,
|
|
52
|
+
ready=None,
|
|
53
|
+
):
|
|
54
|
+
self._values = immutable(values)
|
|
55
|
+
self.name = name
|
|
56
|
+
self._opts = {"index": index.copy(deep=True)}
|
|
57
|
+
self._cursor = cursor or IndicatorCursor(self._values.shape[-1], "complete")
|
|
58
|
+
self._selectors = ()
|
|
59
|
+
self._valid = immutable(np.isfinite(self._values) if valid is None else valid, bool)
|
|
60
|
+
self._required = (
|
|
61
|
+
tuple(range(np.atleast_2d(self._values).shape[0]))
|
|
62
|
+
if required is None
|
|
63
|
+
else tuple(required)
|
|
64
|
+
)
|
|
65
|
+
self._metadata = deepcopy(metadata or {})
|
|
66
|
+
self._ready = immutable(
|
|
67
|
+
np.ones(self._values.shape[-1], dtype=bool) if ready is None else ready, bool
|
|
68
|
+
)
|
|
69
|
+
self._set_required(self._required)
|
|
70
|
+
|
|
71
|
+
def _view(self):
|
|
72
|
+
value = self._values[..., : self._cursor.length]
|
|
73
|
+
for selector in self._selectors:
|
|
74
|
+
value = value[selector]
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
def _index(self):
|
|
78
|
+
indices = np.broadcast_to(
|
|
79
|
+
np.arange(self._cursor.length), self._values.shape[:-1] + (self._cursor.length,)
|
|
80
|
+
)
|
|
81
|
+
for selector in self._selectors:
|
|
82
|
+
indices = indices[selector]
|
|
83
|
+
return self._opts["index"][np.atleast_2d(indices)[0]]
|
|
84
|
+
|
|
85
|
+
def __array__(self, dtype=None, copy=None):
|
|
86
|
+
# Conversion saves only the currently visible snapshot; .base does not expose the complete future array.
|
|
87
|
+
if copy is False:
|
|
88
|
+
raise ValueError("Indicator conversion requires an isolated snapshot; copy=False is not allowed")
|
|
89
|
+
if copy is True:
|
|
90
|
+
return np.array(self._view(), dtype=dtype, copy=True)
|
|
91
|
+
return immutable(self._view(), dtype)
|
|
92
|
+
|
|
93
|
+
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
|
|
94
|
+
if kwargs.get("out") is not None or method == "at":
|
|
95
|
+
raise TypeError("Indicators are read-only; copy to an array before in-place operations")
|
|
96
|
+
values = [
|
|
97
|
+
value._view() if isinstance(value, IndicatorHandle) else value for value in inputs
|
|
98
|
+
]
|
|
99
|
+
return getattr(ufunc, method)(*values, **kwargs)
|
|
100
|
+
|
|
101
|
+
def __getitem__(self, key):
|
|
102
|
+
if isinstance(key, str):
|
|
103
|
+
names = [self.name] if isinstance(self.name, str) else list(self.name)
|
|
104
|
+
if key not in names:
|
|
105
|
+
raise KeyError(key)
|
|
106
|
+
key = names.index(key) if self.ndim == 2 else slice(None)
|
|
107
|
+
value = self._view()[key]
|
|
108
|
+
if not isinstance(value, np.ndarray):
|
|
109
|
+
return value
|
|
110
|
+
result = object.__new__(type(self))
|
|
111
|
+
result.__dict__ = self.__dict__.copy()
|
|
112
|
+
result._selectors = self._selectors + (key,)
|
|
113
|
+
if self.ndim == 2 and isinstance(key, (int, np.integer)):
|
|
114
|
+
result.name = self.name[key] if not isinstance(self.name, str) else self.name
|
|
115
|
+
return result
|
|
116
|
+
|
|
117
|
+
def __len__(self):
|
|
118
|
+
return len(self._view())
|
|
119
|
+
|
|
120
|
+
def __iter__(self):
|
|
121
|
+
for i in range(len(self)):
|
|
122
|
+
yield self[i]
|
|
123
|
+
|
|
124
|
+
def __bool__(self):
|
|
125
|
+
return bool(self[-1])
|
|
126
|
+
|
|
127
|
+
def __float__(self):
|
|
128
|
+
return float(self[-1])
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def shape(self):
|
|
132
|
+
"""Shape of the currently visible prefix; time is the last axis for multiple outputs."""
|
|
133
|
+
return self._view().shape
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def ndim(self):
|
|
137
|
+
"""Output dimensions: 1 for a single channel, 2 for multiple channels."""
|
|
138
|
+
return self._view().ndim
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def dtype(self):
|
|
142
|
+
"""Data type of the underlying read-only numeric buffer."""
|
|
143
|
+
return self._values.dtype
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def s(self):
|
|
147
|
+
"""pandas Series copy of the first channel's current prefix; saved copies do not grow with the cursor."""
|
|
148
|
+
return pd.Series(
|
|
149
|
+
np.atleast_2d(np.asarray(self))[0].copy(), index=self._index(), name=self.name
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def df(self):
|
|
154
|
+
"""pandas DataFrame copy of all channels' current prefixes."""
|
|
155
|
+
value = np.atleast_2d(np.asarray(self))
|
|
156
|
+
names = self.name if isinstance(self.name, (list, tuple)) else [self.name] * len(value)
|
|
157
|
+
return pd.DataFrame(value.T.copy(), index=self._index(), columns=names)
|
|
158
|
+
|
|
159
|
+
def copy(self):
|
|
160
|
+
"""Return a writable NumPy copy of the current prefix that no longer follows the cursor."""
|
|
161
|
+
return np.array(self, copy=True)
|
|
162
|
+
|
|
163
|
+
def astype(self, dtype):
|
|
164
|
+
"""Convert the current prefix to a NumPy array snapshot with dtype; do not retain a dynamic cursor."""
|
|
165
|
+
return np.asarray(self, dtype=dtype)
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def valid(self):
|
|
169
|
+
"""Read-only validity masks for each channel's current prefix; False does not mean numeric zero."""
|
|
170
|
+
value = self._valid[..., : self._cursor.length]
|
|
171
|
+
for selector in self._selectors:
|
|
172
|
+
value = value[selector]
|
|
173
|
+
return immutable(value)
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def ready(self):
|
|
177
|
+
"""Read-only mask indicating whether the current prefix meets minimum-history conditions."""
|
|
178
|
+
return immutable(self._ready[: self._cursor.length])
|
|
179
|
+
|
|
180
|
+
@property
|
|
181
|
+
def metadata(self):
|
|
182
|
+
"""Return a deep copy of provenance metadata; modifications do not affect the registered result."""
|
|
183
|
+
return deepcopy(self._metadata)
|
|
184
|
+
|
|
185
|
+
def _set_required(self, required):
|
|
186
|
+
self._required = tuple(required)
|
|
187
|
+
self._gating = immutable(
|
|
188
|
+
self._ready & np.atleast_2d(self._valid)[list(self._required)].all(axis=0)
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def ready_at(self, bar):
|
|
192
|
+
"""Query readiness of required outputs and minimum-history conditions; does not control strategy callbacks."""
|
|
193
|
+
return bool(self._gating[bar])
|
|
194
|
+
|
|
195
|
+
def first_ready(self):
|
|
196
|
+
"""Return the earliest ready position for required outputs, or total length if never ready; does not control callbacks."""
|
|
197
|
+
positions = np.flatnonzero(self._gating)
|
|
198
|
+
return int(positions[0]) if positions.size else len(self._gating)
|
|
199
|
+
|
|
200
|
+
def __getstate__(self):
|
|
201
|
+
return self.__dict__
|
|
202
|
+
|
|
203
|
+
def __setstate__(self, state):
|
|
204
|
+
self.__dict__.update(state)
|
|
205
|
+
self._values = immutable(self._values)
|
|
206
|
+
self._valid = immutable(self._valid, bool)
|
|
207
|
+
self._ready = immutable(self._ready, bool)
|
|
208
|
+
self._gating = immutable(self._gating, bool)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass(frozen=True)
|
|
212
|
+
class Parameter:
|
|
213
|
+
"""Indicator parameter definition: default/kind define default value and type; minimum/maximum/choices constrain allowed values."""
|
|
214
|
+
|
|
215
|
+
default: object
|
|
216
|
+
kind: str
|
|
217
|
+
minimum: float | None = None
|
|
218
|
+
maximum: float | None = None
|
|
219
|
+
choices: tuple = ()
|
|
220
|
+
|
|
221
|
+
def normalize(self, value, indicator, name):
|
|
222
|
+
"""Validate value type, bounds, and choices and convert to a native type; failures raise IndicatorError, located by indicator/name."""
|
|
223
|
+
types = {
|
|
224
|
+
"int": (int, np.integer),
|
|
225
|
+
"float": (int, float, np.integer, np.floating),
|
|
226
|
+
"bool": (bool,),
|
|
227
|
+
"str": (str,),
|
|
228
|
+
}
|
|
229
|
+
valid = isinstance(value, types[self.kind])
|
|
230
|
+
if self.kind in ("int", "float"):
|
|
231
|
+
valid = valid and not isinstance(value, (bool, np.bool_))
|
|
232
|
+
if valid:
|
|
233
|
+
try:
|
|
234
|
+
valid = isfinite(value)
|
|
235
|
+
except OverflowError:
|
|
236
|
+
valid = False
|
|
237
|
+
if valid and self.minimum is not None:
|
|
238
|
+
valid = value >= self.minimum
|
|
239
|
+
if valid and self.maximum is not None:
|
|
240
|
+
valid = value <= self.maximum
|
|
241
|
+
if valid and self.choices:
|
|
242
|
+
valid = value in self.choices
|
|
243
|
+
if not valid:
|
|
244
|
+
raise IndicatorError(
|
|
245
|
+
"invalid-parameter", indicator, f"{name}: {value!r}", parameter=name
|
|
246
|
+
)
|
|
247
|
+
return {"int": int, "float": float, "bool": bool, "str": str}[self.kind](value)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass(frozen=True)
|
|
251
|
+
class IndicatorSpec:
|
|
252
|
+
"""Reviewed indicator definition containing identity, inputs/outputs, parameter domains, backends, validity, and history requirements."""
|
|
253
|
+
|
|
254
|
+
id: str
|
|
255
|
+
source: str
|
|
256
|
+
category: str
|
|
257
|
+
inputs: tuple[str, ...]
|
|
258
|
+
outputs: tuple[str, ...]
|
|
259
|
+
parameters: tuple[tuple[str, Parameter], ...] = ()
|
|
260
|
+
version: str = "1"
|
|
261
|
+
status: str = "available"
|
|
262
|
+
reason: str = ""
|
|
263
|
+
required: tuple[str, ...] | None = None
|
|
264
|
+
fixed: tuple[tuple[str, object], ...] = ()
|
|
265
|
+
|
|
266
|
+
minimum: int = 1
|
|
267
|
+
windows: tuple[str, ...] = ()
|
|
268
|
+
dtype: str = "float64"
|
|
269
|
+
unit: str = "native"
|
|
270
|
+
missing_policy: str = "native-v1"
|
|
271
|
+
causal: bool = True
|
|
272
|
+
|
|
273
|
+
aliases: tuple[tuple[str, str], ...] = ()
|
|
274
|
+
history_sum: tuple = ()
|
|
275
|
+
history_offset: int = 0
|
|
276
|
+
kind: str = "indicator"
|
|
277
|
+
input_args: tuple[str, ...] = ()
|
|
278
|
+
input_defaults: tuple[tuple[str, str], ...] = ()
|
|
279
|
+
default_backend: str = "pandas-ta-classic.native"
|
|
280
|
+
validity: str = "current-inputs"
|
|
281
|
+
|
|
282
|
+
def history_required(self, parameters):
|
|
283
|
+
"""Calculate minimum history length from normalized parameters; this does not imply every output is valid."""
|
|
284
|
+
return max(
|
|
285
|
+
[
|
|
286
|
+
self.minimum,
|
|
287
|
+
*(parameters[name] for name in self.windows),
|
|
288
|
+
self.history_offset
|
|
289
|
+
+ sum(parameters[name] * coefficient for name, coefficient in self.history_sum),
|
|
290
|
+
]
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def __post_init__(self):
|
|
294
|
+
if not self.id or not self.outputs or len(set(self.outputs)) != len(self.outputs):
|
|
295
|
+
raise ValueError("Indicator identity and unique outputs must not be empty")
|
|
296
|
+
aliases = [name for name, _ in self.aliases]
|
|
297
|
+
if len(set(aliases)) != len(aliases) or any(
|
|
298
|
+
name in self.outputs or target not in self.outputs for name, target in self.aliases
|
|
299
|
+
):
|
|
300
|
+
raise ValueError("Duplicate output aliases or invalid targets")
|
|
301
|
+
if self.kind not in ("indicator", "utility", "pattern") or self.validity not in (
|
|
302
|
+
"current-inputs",
|
|
303
|
+
"output",
|
|
304
|
+
):
|
|
305
|
+
raise ValueError("Unknown indicator kind or validity policy")
|
|
306
|
+
if len(set(self.input_args)) != len(self.input_args) or not set(self.input_args) <= set(
|
|
307
|
+
self.inputs
|
|
308
|
+
):
|
|
309
|
+
raise ValueError("Public input parameters must be unique and declared")
|
|
310
|
+
defaults = dict(self.input_defaults)
|
|
311
|
+
if len(defaults) != len(self.input_defaults) or not defaults.keys() <= set(self.inputs):
|
|
312
|
+
raise ValueError("Default inputs must be unique and declared")
|
|
313
|
+
names = [name for name, _ in self.parameters]
|
|
314
|
+
if len(set(names)) != len(names) or set(names) & {"inputs", "backend", "required_outputs"}:
|
|
315
|
+
raise ValueError("Duplicate parameters or parameters reserved for execution options")
|
|
316
|
+
if self.required is not None and not set(self.required) <= set(self.outputs):
|
|
317
|
+
raise ValueError("Required outputs are not declared in the definition")
|
|
318
|
+
|
|
319
|
+
def normalize(self, parameters):
|
|
320
|
+
"""Validate the parameters mapping, fill catalog defaults, and check parameter relationships; invalid values raise IndicatorError."""
|
|
321
|
+
definitions = dict(self.parameters)
|
|
322
|
+
unknown = parameters.keys() - definitions.keys()
|
|
323
|
+
if unknown:
|
|
324
|
+
raise IndicatorError("invalid-parameter", self.id, f"Unknown parameter: {sorted(unknown)}")
|
|
325
|
+
values = {
|
|
326
|
+
name: rule.normalize(parameters.get(name, rule.default), self.id, name)
|
|
327
|
+
for name, rule in self.parameters
|
|
328
|
+
}
|
|
329
|
+
if "fast" in values and "slow" in values and values["fast"] >= values["slow"]:
|
|
330
|
+
raise IndicatorError("invalid-parameter", self.id, "fast must be less than slow")
|
|
331
|
+
if "ddof" in values and "length" in values and values["ddof"] >= values["length"]:
|
|
332
|
+
raise IndicatorError("invalid-parameter", self.id, "ddof must be less than length")
|
|
333
|
+
for lower, upper in (("minperiod", "maxperiod"), ("slowlimit", "fastlimit")):
|
|
334
|
+
if lower in values and upper in values and values[lower] >= values[upper]:
|
|
335
|
+
raise IndicatorError("invalid-parameter", self.id, f"{lower} must be less than {upper}")
|
|
336
|
+
if "kc_scalar_wide" in values and not (
|
|
337
|
+
values["kc_scalar_wide"] > values["kc_scalar_normal"] > values["kc_scalar_narrow"]
|
|
338
|
+
):
|
|
339
|
+
raise IndicatorError(
|
|
340
|
+
"invalid-parameter", self.id, "KC coefficients must satisfy wide > normal > narrow"
|
|
341
|
+
)
|
|
342
|
+
if self.id == "ta.frama" and values["length"] % 2:
|
|
343
|
+
raise IndicatorError("invalid-parameter", self.id, "length must be even")
|
|
344
|
+
return values
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""ta.barssince: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from ..calculus import condition as validate_condition
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def compute(*, condition):
|
|
10
|
+
condition = validate_condition(condition, "ta.barssince")
|
|
11
|
+
result = np.full(len(condition), np.nan)
|
|
12
|
+
last = None
|
|
13
|
+
for i, value in enumerate(condition):
|
|
14
|
+
if not np.isfinite(value):
|
|
15
|
+
last = None
|
|
16
|
+
else:
|
|
17
|
+
if value == 1:
|
|
18
|
+
last = i
|
|
19
|
+
if last is not None:
|
|
20
|
+
result[i] = i - last
|
|
21
|
+
return pd.Series(result, index=condition.index, name="barssince")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""ta.cross: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import clean
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, left, right):
|
|
7
|
+
left, right = clean(left), clean(right)
|
|
8
|
+
a, b = left.shift(1), right.shift(1)
|
|
9
|
+
up = (left > right) & (a <= b)
|
|
10
|
+
down = (left < right) & (a >= b)
|
|
11
|
+
valid = left.notna() & right.notna() & a.notna() & b.notna()
|
|
12
|
+
return (up | down).astype(float).where(valid).rename("cross")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""ta.crossover: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import clean
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, left, right):
|
|
7
|
+
left, right = clean(left), clean(right)
|
|
8
|
+
a, b = left.shift(1), right.shift(1)
|
|
9
|
+
up = (left > right) & (a <= b)
|
|
10
|
+
valid = left.notna() & right.notna() & a.notna() & b.notna()
|
|
11
|
+
return (up).astype(float).where(valid).rename("crossover")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""ta.crossunder: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import clean
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, left, right):
|
|
7
|
+
left, right = clean(left), clean(right)
|
|
8
|
+
a, b = left.shift(1), right.shift(1)
|
|
9
|
+
down = (left < right) & (a >= b)
|
|
10
|
+
valid = left.notna() & right.notna() & a.notna() & b.notna()
|
|
11
|
+
return (down).astype(float).where(valid).rename("crossunder")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""ta.falling: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import clean
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, source, length):
|
|
7
|
+
source = clean(source)
|
|
8
|
+
previous = source.shift(1).rolling(length).min()
|
|
9
|
+
result = (source < previous).astype(float)
|
|
10
|
+
return result.where(source.notna() & previous.notna()).rename("falling")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""ta.highestbars: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from ..calculus import clean
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compute(*, source, length):
|
|
12
|
+
source = clean(source)
|
|
13
|
+
values = source.to_numpy()
|
|
14
|
+
result = np.full(len(source), np.nan)
|
|
15
|
+
queue = deque()
|
|
16
|
+
last_missing = -1
|
|
17
|
+
for i, value in enumerate(values):
|
|
18
|
+
if not np.isfinite(value):
|
|
19
|
+
last_missing = i
|
|
20
|
+
queue.clear()
|
|
21
|
+
continue
|
|
22
|
+
while queue and values[queue[-1]] <= value:
|
|
23
|
+
queue.pop()
|
|
24
|
+
queue.append(i)
|
|
25
|
+
if queue[0] <= i - length:
|
|
26
|
+
queue.popleft()
|
|
27
|
+
if i >= length - 1 and i - last_missing >= length:
|
|
28
|
+
result[i] = queue[0] - i
|
|
29
|
+
return pd.Series(result, index=source.index, name="highestbars")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""ta.lowestbars: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from ..calculus import clean
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compute(*, source, length):
|
|
12
|
+
source = clean(source)
|
|
13
|
+
values = source.to_numpy()
|
|
14
|
+
result = np.full(len(source), np.nan)
|
|
15
|
+
queue = deque()
|
|
16
|
+
last_missing = -1
|
|
17
|
+
for i, value in enumerate(values):
|
|
18
|
+
if not np.isfinite(value):
|
|
19
|
+
last_missing = i
|
|
20
|
+
queue.clear()
|
|
21
|
+
continue
|
|
22
|
+
while queue and values[queue[-1]] >= value:
|
|
23
|
+
queue.pop()
|
|
24
|
+
queue.append(i)
|
|
25
|
+
if queue[0] <= i - length:
|
|
26
|
+
queue.popleft()
|
|
27
|
+
if i >= length - 1 and i - last_missing >= length:
|
|
28
|
+
result[i] = queue[0] - i
|
|
29
|
+
return pd.Series(result, index=source.index, name="lowestbars")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""ta.percentile_linear_interpolation: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import clean
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, source, length, percentage):
|
|
7
|
+
return (
|
|
8
|
+
clean(source)
|
|
9
|
+
.rolling(length)
|
|
10
|
+
.quantile(percentage / 100)
|
|
11
|
+
.rename("percentile_linear_interpolation")
|
|
12
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""ta.percentile_nearest_rank: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from math import ceil
|
|
4
|
+
|
|
5
|
+
from ..calculus import clean
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def compute(*, source, length, percentage):
|
|
9
|
+
rank = max(0, ceil(percentage * length / 100) - 1)
|
|
10
|
+
q = rank / max(1, length - 1)
|
|
11
|
+
return (
|
|
12
|
+
clean(source)
|
|
13
|
+
.rolling(length)
|
|
14
|
+
.quantile(q, interpolation="nearest")
|
|
15
|
+
.rename("percentile_nearest_rank")
|
|
16
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""ta.rising: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import clean
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, source, length):
|
|
7
|
+
source = clean(source)
|
|
8
|
+
previous = source.shift(1).rolling(length).max()
|
|
9
|
+
result = (source > previous).astype(float)
|
|
10
|
+
return result.where(source.notna() & previous.notna()).rename("rising")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""ta.rolling_count: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from ..calculus import condition as validate_condition
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def compute(*, condition, length):
|
|
7
|
+
return (
|
|
8
|
+
validate_condition(condition, "ta.rolling_count")
|
|
9
|
+
.rolling(length)
|
|
10
|
+
.sum()
|
|
11
|
+
.rename("rolling_count")
|
|
12
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""ta.valuewhen: Fixed causal sequence contract."""
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from ..calculus import clean
|
|
9
|
+
from ..calculus import condition as validate_condition
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def compute(*, condition, source, occurrence):
|
|
13
|
+
condition = validate_condition(condition, "ta.valuewhen")
|
|
14
|
+
source = clean(source)
|
|
15
|
+
result = np.full(len(source), np.nan)
|
|
16
|
+
events = deque(maxlen=occurrence + 1)
|
|
17
|
+
for i, value in enumerate(condition):
|
|
18
|
+
if not np.isfinite(value):
|
|
19
|
+
events.clear()
|
|
20
|
+
else:
|
|
21
|
+
if value == 1:
|
|
22
|
+
events.append(source.iloc[i])
|
|
23
|
+
if len(events) > occurrence:
|
|
24
|
+
result[i] = events[0]
|
|
25
|
+
return pd.Series(result, index=source.index, name="valuewhen")
|
|
File without changes
|