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,218 @@
|
|
|
1
|
+
"""Native market-data views, indicator arrays, and warmup helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
from numbers import Number
|
|
7
|
+
from typing import cast
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from openbacktesting.helpers import _set_legacy_module
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _data_period(index) -> pd.Timedelta | Number:
|
|
16
|
+
"""Return data index period as pd.Timedelta"""
|
|
17
|
+
values = pd.Series(index[-100:])
|
|
18
|
+
return values.diff().dropna().median()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _strategy_indicators(strategy):
|
|
22
|
+
return {
|
|
23
|
+
attr: indicator
|
|
24
|
+
for attr, indicator in strategy.__dict__.items()
|
|
25
|
+
if isinstance(indicator, _Indicator)
|
|
26
|
+
}.items()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _indicator_warmup_nbars(strategy):
|
|
30
|
+
"""Indicator-readiness diagnostic for the legacy entry point; no longer controls next scheduling or return baselines."""
|
|
31
|
+
if strategy is None:
|
|
32
|
+
return 0
|
|
33
|
+
if hasattr(strategy, "_indicator_cursor"):
|
|
34
|
+
return max((indicator.first_ready() for indicator in strategy._indicators), default=0)
|
|
35
|
+
nbars = max(
|
|
36
|
+
(
|
|
37
|
+
np.isnan(indicator.astype(float)).argmin(axis=-1).max()
|
|
38
|
+
for _, indicator in _strategy_indicators(strategy)
|
|
39
|
+
if indicator._opts.get("warmup", True)
|
|
40
|
+
),
|
|
41
|
+
default=0,
|
|
42
|
+
)
|
|
43
|
+
return nbars
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _Array(np.ndarray):
|
|
47
|
+
"""
|
|
48
|
+
ndarray extended to supply .name and other arbitrary properties
|
|
49
|
+
in ._opts dict.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __new__(cls, array, *, name=None, **kwargs):
|
|
53
|
+
obj = np.asarray(array).view(cls)
|
|
54
|
+
obj.name = name or array.name
|
|
55
|
+
obj._opts = kwargs
|
|
56
|
+
return obj
|
|
57
|
+
|
|
58
|
+
def __array_finalize__(self, obj):
|
|
59
|
+
if obj is not None:
|
|
60
|
+
self.name = getattr(obj, "name", "")
|
|
61
|
+
self._opts = getattr(obj, "_opts", {})
|
|
62
|
+
|
|
63
|
+
# Make sure properties name and _opts are carried over
|
|
64
|
+
# when (un-)pickling.
|
|
65
|
+
def __reduce__(self):
|
|
66
|
+
value = super().__reduce__()
|
|
67
|
+
return value[:2] + (value[2] + (self.__dict__,),)
|
|
68
|
+
|
|
69
|
+
def __setstate__(self, state):
|
|
70
|
+
self.__dict__.update(state[-1])
|
|
71
|
+
super().__setstate__(state[:-1])
|
|
72
|
+
|
|
73
|
+
def __bool__(self):
|
|
74
|
+
try:
|
|
75
|
+
return bool(self[-1])
|
|
76
|
+
except IndexError:
|
|
77
|
+
return super().__bool__()
|
|
78
|
+
|
|
79
|
+
def __float__(self):
|
|
80
|
+
try:
|
|
81
|
+
return float(self[-1])
|
|
82
|
+
except IndexError:
|
|
83
|
+
return super().__float__()
|
|
84
|
+
|
|
85
|
+
def to_series(self):
|
|
86
|
+
warnings.warn("`.to_series()` is deprecated. For pd.Series conversion, use accessor `.s`")
|
|
87
|
+
return self.s
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def s(self) -> pd.Series:
|
|
91
|
+
values = np.atleast_2d(self)
|
|
92
|
+
index = self._opts["index"][: values.shape[1]]
|
|
93
|
+
return pd.Series(values[0], index=index, name=self.name)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def df(self) -> pd.DataFrame:
|
|
97
|
+
values = np.atleast_2d(np.asarray(self))
|
|
98
|
+
index = self._opts["index"][: values.shape[1]]
|
|
99
|
+
df = pd.DataFrame(values.T, index=index, columns=[self.name] * len(values))
|
|
100
|
+
return df
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class _Indicator(_Array):
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class _Data:
|
|
108
|
+
"""
|
|
109
|
+
A data array accessor. Provides access to OHLCV "columns"
|
|
110
|
+
as a standard `pd.DataFrame` would, except it's not a DataFrame
|
|
111
|
+
and the returned "series" are _not_ `pd.Series` but `np.ndarray`
|
|
112
|
+
for performance reasons.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
def __init__(self, df: pd.DataFrame):
|
|
116
|
+
self.__df = df
|
|
117
|
+
self.__len = len(df) # Current length
|
|
118
|
+
self.__pip: float | None = None
|
|
119
|
+
self.__cache: dict[str, _Array] = {}
|
|
120
|
+
self.__arrays: dict[str, _Array] = {}
|
|
121
|
+
self._update()
|
|
122
|
+
|
|
123
|
+
def __getitem__(self, item):
|
|
124
|
+
return self.__get_array(item)
|
|
125
|
+
|
|
126
|
+
def __getattr__(self, item):
|
|
127
|
+
try:
|
|
128
|
+
return self.__get_array(item)
|
|
129
|
+
except KeyError:
|
|
130
|
+
raise AttributeError(f"Column '{item}' not in data") from None
|
|
131
|
+
|
|
132
|
+
def _set_length(self, length):
|
|
133
|
+
self.__len = length
|
|
134
|
+
self.__cache.clear()
|
|
135
|
+
|
|
136
|
+
def _update(self):
|
|
137
|
+
index = self.__df.index.copy()
|
|
138
|
+
self.__arrays = {col: _Array(arr, index=index) for col, arr in self.__df.items()}
|
|
139
|
+
# Leave index as Series because pd.Timestamp nicer API to work with
|
|
140
|
+
self.__arrays["__index"] = index
|
|
141
|
+
|
|
142
|
+
def __repr__(self):
|
|
143
|
+
i = min(self.__len, len(self.__df)) - 1
|
|
144
|
+
index = self.__arrays["__index"][i]
|
|
145
|
+
items = ", ".join(f"{k}={v}" for k, v in self.__df.iloc[i].items())
|
|
146
|
+
return f"<Data i={i} ({index}) {items}>"
|
|
147
|
+
|
|
148
|
+
def __len__(self):
|
|
149
|
+
return self.__len
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def df(self) -> pd.DataFrame:
|
|
153
|
+
return self.__df.iloc[: self.__len] if self.__len < len(self.__df) else self.__df
|
|
154
|
+
|
|
155
|
+
def _full_frame(self) -> pd.DataFrame:
|
|
156
|
+
"""Used for internal provenance capture without changing the visible length of public data."""
|
|
157
|
+
return self.__df
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def pip(self) -> float:
|
|
161
|
+
if self.__pip is None:
|
|
162
|
+
self.__pip = float(
|
|
163
|
+
10
|
|
164
|
+
** -np.median(
|
|
165
|
+
[len(s.partition(".")[-1]) for s in self.__arrays["Close"].astype(str)]
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
return self.__pip
|
|
169
|
+
|
|
170
|
+
def __get_array(self, key) -> _Array:
|
|
171
|
+
arr = self.__cache.get(key)
|
|
172
|
+
if arr is None:
|
|
173
|
+
arr = self.__cache[key] = cast(_Array, self.__arrays[key][: self.__len])
|
|
174
|
+
return arr
|
|
175
|
+
|
|
176
|
+
def _current_value(self, key: str):
|
|
177
|
+
# Known fast path to avoid needless __get_array reslicing
|
|
178
|
+
assert self.__len >= 0, self
|
|
179
|
+
return self.__arrays[key][self.__len - 1]
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def Open(self) -> _Array:
|
|
183
|
+
return self.__get_array("Open")
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def High(self) -> _Array:
|
|
187
|
+
return self.__get_array("High")
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def Low(self) -> _Array:
|
|
191
|
+
return self.__get_array("Low")
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def Close(self) -> _Array:
|
|
195
|
+
return self.__get_array("Close")
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def Volume(self) -> _Array:
|
|
199
|
+
return self.__get_array("Volume")
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def index(self) -> pd.DatetimeIndex:
|
|
203
|
+
return self.__get_array("__index")
|
|
204
|
+
|
|
205
|
+
# Make pickling in Backtest.optimize() work with our catch-all __getattr__
|
|
206
|
+
def __getstate__(self):
|
|
207
|
+
return self.__dict__
|
|
208
|
+
|
|
209
|
+
def __setstate__(self, state):
|
|
210
|
+
self.__dict__ = state
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
_set_legacy_module(_data_period, "openbacktesting._util")
|
|
214
|
+
_set_legacy_module(_strategy_indicators, "openbacktesting._util")
|
|
215
|
+
_set_legacy_module(_indicator_warmup_nbars, "openbacktesting._util")
|
|
216
|
+
_set_legacy_module(_Array, "openbacktesting._util")
|
|
217
|
+
_set_legacy_module(_Indicator, "openbacktesting._util")
|
|
218
|
+
_set_legacy_module(_Data, "openbacktesting._util")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Existing implementation moved from lib: random_ohlc_data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from openbacktesting.helpers import _set_legacy_module
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def random_ohlc_data(
|
|
13
|
+
example_data: pd.DataFrame, *, frac=1.0, random_state: int | None = None
|
|
14
|
+
) -> Generator[pd.DataFrame, None, None]:
|
|
15
|
+
"""Iteratively generate random samples preserving basic market-data structure; these are not real-market forecasts.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
OHLC data generator. The generated OHLC data has basic
|
|
19
|
+
[descriptive statistics](https://en.wikipedia.org/wiki/Descriptive_statistics)
|
|
20
|
+
similar to the provided `example_data`.
|
|
21
|
+
|
|
22
|
+
`frac` is a fraction of data to sample (with replacement). Values greater
|
|
23
|
+
than 1 result in oversampling.
|
|
24
|
+
|
|
25
|
+
Such random data can be effectively used for stress testing trading
|
|
26
|
+
strategy robustness, Monte Carlo simulations, significance testing, etc.
|
|
27
|
+
|
|
28
|
+
>>> from openbacktesting.test import EURUSD
|
|
29
|
+
>>> ohlc_generator = random_ohlc_data(EURUSD)
|
|
30
|
+
>>> next(ohlc_generator) # returns new random data
|
|
31
|
+
...
|
|
32
|
+
>>> next(ohlc_generator) # returns new random data
|
|
33
|
+
...
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def shuffle(x):
|
|
37
|
+
return x.sample(frac=frac, replace=frac > 1, random_state=random_state)
|
|
38
|
+
|
|
39
|
+
if len(example_data.columns.intersection({"Open", "High", "Low", "Close"})) != 4:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"`data` must be a pandas.DataFrame with columns 'Open', 'High', 'Low', 'Close'"
|
|
42
|
+
)
|
|
43
|
+
while True:
|
|
44
|
+
df = shuffle(example_data)
|
|
45
|
+
df.index = example_data.index
|
|
46
|
+
padding = df.Close - df.Open.shift(-1)
|
|
47
|
+
gaps = shuffle(example_data.Open.shift(-1) - example_data.Close)
|
|
48
|
+
deltas = (padding + gaps).shift(1).fillna(0).cumsum()
|
|
49
|
+
for key in ("Open", "High", "Low", "Close"):
|
|
50
|
+
df[key] += deltas
|
|
51
|
+
yield df
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_set_legacy_module(random_ohlc_data, "openbacktesting.lib")
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Per-run market-data snapshot and stable series; does not register indicators."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from openbacktesting.data.core import _Data
|
|
7
|
+
from openbacktesting.indicators.types import (
|
|
8
|
+
IndicatorCursor,
|
|
9
|
+
IndicatorError,
|
|
10
|
+
IndicatorHandle,
|
|
11
|
+
immutable,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
PRICE_COLUMNS = {"open": "Open", "high": "High", "low": "Low", "close": "Close", "volume": "Volume"}
|
|
15
|
+
DERIVED = ("hl2", "hlc3", "hlcc4")
|
|
16
|
+
DATA_NAMES = frozenset((*PRICE_COLUMNS, *DERIVED, "data_index", "df", "pip"))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StrategyData:
|
|
20
|
+
def __init__(self, data: _Data, cursor: IndicatorCursor):
|
|
21
|
+
self._data = data
|
|
22
|
+
self._cursor = cursor
|
|
23
|
+
self._baseline: pd.DataFrame | None = None
|
|
24
|
+
self._values: dict[str, np.ndarray] = {}
|
|
25
|
+
self._handles: dict[str, IndicatorHandle] = {}
|
|
26
|
+
|
|
27
|
+
def validate(self):
|
|
28
|
+
"""Check only at the initialization boundary; do not scan all data on each bar."""
|
|
29
|
+
if self._baseline is None or self._cursor.phase != "init":
|
|
30
|
+
return
|
|
31
|
+
frame = self._data._full_frame()
|
|
32
|
+
columns = frame.columns[frame.columns.isin(PRICE_COLUMNS.values())]
|
|
33
|
+
current = frame.loc[:, columns]
|
|
34
|
+
if (
|
|
35
|
+
not current.equals(self._baseline)
|
|
36
|
+
or not current.index.identical(self._baseline.index)
|
|
37
|
+
or not current.columns.identical(self._baseline.columns)
|
|
38
|
+
):
|
|
39
|
+
raise IndicatorError(
|
|
40
|
+
"data-source-mutated",
|
|
41
|
+
"strategy.data",
|
|
42
|
+
"OHLCV or index changed; finish market-data preprocessing before first reading a market-data series",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def _capture(self):
|
|
46
|
+
frame = self._data._full_frame()
|
|
47
|
+
if not frame.index.is_unique:
|
|
48
|
+
raise IndicatorError("invalid-data-source", "strategy.data", "Market-data index must be unique")
|
|
49
|
+
columns = frame.columns[frame.columns.isin(PRICE_COLUMNS.values())]
|
|
50
|
+
if len(columns) != 5 or set(columns) != set(PRICE_COLUMNS.values()):
|
|
51
|
+
raise IndicatorError("invalid-data-source", "strategy.data", "Unique OHLCV columns are required")
|
|
52
|
+
baseline = frame.loc[:, columns].copy(deep=True)
|
|
53
|
+
values = {}
|
|
54
|
+
for name, column in PRICE_COLUMNS.items():
|
|
55
|
+
series = baseline[column]
|
|
56
|
+
if pd.api.types.is_bool_dtype(series.dtype) or not (
|
|
57
|
+
pd.api.types.is_integer_dtype(series.dtype)
|
|
58
|
+
or pd.api.types.is_float_dtype(series.dtype)
|
|
59
|
+
):
|
|
60
|
+
raise IndicatorError(
|
|
61
|
+
"invalid-data-source", "strategy.data", f"{column} must be a real-valued market-data column"
|
|
62
|
+
)
|
|
63
|
+
values[name] = immutable(series.to_numpy(dtype=np.float64, na_value=np.nan))
|
|
64
|
+
self._baseline = baseline
|
|
65
|
+
self._values = values
|
|
66
|
+
|
|
67
|
+
def _derive(self, name):
|
|
68
|
+
high, low, close = (self._values[key] for key in ("high", "low", "close"))
|
|
69
|
+
valid = np.isfinite(high) & np.isfinite(low)
|
|
70
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
71
|
+
if name == "hl2":
|
|
72
|
+
values = (high + low) / 2
|
|
73
|
+
else:
|
|
74
|
+
valid &= np.isfinite(close)
|
|
75
|
+
if name == "hlc3":
|
|
76
|
+
values = ((high + low) + close) / 3
|
|
77
|
+
else:
|
|
78
|
+
values = ((high + low) + (close + close)) / 4
|
|
79
|
+
return np.where(valid & np.isfinite(values), values, np.nan)
|
|
80
|
+
|
|
81
|
+
def get(self, name: str) -> IndicatorHandle:
|
|
82
|
+
self.validate()
|
|
83
|
+
if self._baseline is None:
|
|
84
|
+
self._capture()
|
|
85
|
+
if name not in self._handles:
|
|
86
|
+
values = self._values[name] if name in PRICE_COLUMNS else self._derive(name)
|
|
87
|
+
self._handles[name] = IndicatorHandle(
|
|
88
|
+
values,
|
|
89
|
+
name=PRICE_COLUMNS.get(name, name),
|
|
90
|
+
index=self._baseline.index,
|
|
91
|
+
cursor=self._cursor,
|
|
92
|
+
metadata={"kind": "data-source", "field": name, "version": "1"},
|
|
93
|
+
)
|
|
94
|
+
return self._handles[name]
|
|
95
|
+
|
|
96
|
+
def __setstate__(self, state):
|
|
97
|
+
self.__dict__.update(state)
|
|
98
|
+
self._values = {name: immutable(value) for name, value in self._values.items()}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
from openbacktesting.helpers import _set_legacy_module
|
|
102
|
+
|
|
103
|
+
_set_legacy_module(StrategyData, "openbacktesting.strategy_data")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Chart timeframe units and immutable values; no market-data inference or resampling."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TimeframeUnit(StrEnum):
|
|
8
|
+
"""Time-bar units; calendar units are not implicitly converted to fixed seconds."""
|
|
9
|
+
|
|
10
|
+
SECOND = "second"
|
|
11
|
+
MINUTE = "minute"
|
|
12
|
+
DAY = "day"
|
|
13
|
+
WEEK = "week"
|
|
14
|
+
MONTH = "month"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _positive_integer(value: int) -> int:
|
|
18
|
+
if type(value) is not int:
|
|
19
|
+
raise TypeError("Timeframe multiplier must be an integer; bool and floats are not accepted")
|
|
20
|
+
if value <= 0:
|
|
21
|
+
raise ValueError("Timeframe multiplier must be a positive integer")
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class Timeframe:
|
|
27
|
+
"""Timeframe value; the host validates data-source support, and not all combinations are guaranteed to have market data."""
|
|
28
|
+
|
|
29
|
+
unit: TimeframeUnit
|
|
30
|
+
multiplier: int
|
|
31
|
+
|
|
32
|
+
def __post_init__(self):
|
|
33
|
+
if not isinstance(self.unit, TimeframeUnit):
|
|
34
|
+
raise TypeError("unit must be a TimeframeUnit")
|
|
35
|
+
_positive_integer(self.multiplier)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def seconds(cls, value: int) -> "Timeframe":
|
|
39
|
+
"""Create a seconds timeframe."""
|
|
40
|
+
return cls(TimeframeUnit.SECOND, value)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def minutes(cls, value: int) -> "Timeframe":
|
|
44
|
+
"""Create a minutes timeframe."""
|
|
45
|
+
return cls(TimeframeUnit.MINUTE, value)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def hours(cls, value: int) -> "Timeframe":
|
|
49
|
+
"""Normalize hours to minutes; 24 hours is not a daily timeframe."""
|
|
50
|
+
return cls.minutes(_positive_integer(value) * 60)
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def days(cls, value: int) -> "Timeframe":
|
|
54
|
+
"""Create a calendar-day timeframe."""
|
|
55
|
+
return cls(TimeframeUnit.DAY, value)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def weeks(cls, value: int) -> "Timeframe":
|
|
59
|
+
"""Create a calendar-week timeframe."""
|
|
60
|
+
return cls(TimeframeUnit.WEEK, value)
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def months(cls, value: int) -> "Timeframe":
|
|
64
|
+
"""Create a calendar-month timeframe."""
|
|
65
|
+
return cls(TimeframeUnit.MONTH, value)
|
|
66
|
+
|
|
67
|
+
def snapshot(self) -> dict:
|
|
68
|
+
"""Return independent plain unit/multiplier values."""
|
|
69
|
+
return {"unit": self.unit.value, "multiplier": self.multiplier}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Compatibility entry point for old native-kernel paths; definitions reside in the corresponding responsibility modules."""
|
|
2
|
+
|
|
3
|
+
from openbacktesting.broker.core import (
|
|
4
|
+
Order,
|
|
5
|
+
Position,
|
|
6
|
+
Trade,
|
|
7
|
+
__pdoc__, # noqa: F401 — preserve compatibility exports at the original path
|
|
8
|
+
_Broker, # noqa: F401 — preserve compatibility exports at the original path
|
|
9
|
+
_OutOfMoneyError, # noqa: F401 — preserve compatibility exports at the original path
|
|
10
|
+
)
|
|
11
|
+
from openbacktesting.data.core import (
|
|
12
|
+
_Data, # noqa: F401 — preserve compatibility exports at the original path
|
|
13
|
+
_Indicator, # noqa: F401 — preserve compatibility exports at the original path
|
|
14
|
+
_indicator_warmup_nbars, # noqa: F401 — preserve compatibility exports at the original path
|
|
15
|
+
_strategy_indicators, # noqa: F401 — preserve compatibility exports at the original path
|
|
16
|
+
)
|
|
17
|
+
from openbacktesting.helpers import (
|
|
18
|
+
_as_str, # noqa: F401 — preserve compatibility exports at the original path
|
|
19
|
+
patch, # noqa: F401 — preserve compatibility exports at the original path
|
|
20
|
+
try_, # noqa: F401 — preserve compatibility exports at the original path
|
|
21
|
+
)
|
|
22
|
+
from openbacktesting.portfolio.stats import (
|
|
23
|
+
compute_stats, # noqa: F401 — preserve compatibility exports at the original path
|
|
24
|
+
dummy_stats, # noqa: F401 — preserve compatibility exports at the original path
|
|
25
|
+
)
|
|
26
|
+
from openbacktesting.simulation.core import Backtest
|
|
27
|
+
from openbacktesting.simulation.parallel import (
|
|
28
|
+
SharedMemoryManager, # noqa: F401 — preserve compatibility exports at the original path
|
|
29
|
+
_batch, # noqa: F401 — preserve compatibility exports at the original path
|
|
30
|
+
)
|
|
31
|
+
from openbacktesting.strategy.base import Strategy
|
|
32
|
+
from openbacktesting.util import _tqdm # noqa: F401 — preserve compatibility exports at the original path
|
|
33
|
+
|
|
34
|
+
__all__ = ["Strategy", "Position", "Order", "Trade", "Backtest"] # noqa: RUF022 — preserve the original public export order
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Stateless helpers and compatibility with old object paths."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
from numbers import Number
|
|
8
|
+
from types import FunctionType
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _set_legacy_module(obj, module):
|
|
14
|
+
"""Preserve old qualified names used by pickle when moving definitions; do not create replacement classes."""
|
|
15
|
+
original = obj.__module__
|
|
16
|
+
obj.__module__ = module
|
|
17
|
+
if isinstance(obj, type):
|
|
18
|
+
for member in vars(obj).values():
|
|
19
|
+
if isinstance(member, type) and member.__module__ == original:
|
|
20
|
+
_set_legacy_module(member, module)
|
|
21
|
+
if isinstance(member, (staticmethod, classmethod)):
|
|
22
|
+
functions = (member.__func__,)
|
|
23
|
+
elif isinstance(member, property):
|
|
24
|
+
functions = (member.fget, member.fset, member.fdel)
|
|
25
|
+
else:
|
|
26
|
+
functions = (member, getattr(member, "func", None))
|
|
27
|
+
for function in functions:
|
|
28
|
+
if isinstance(function, FunctionType) and function.__module__ == original:
|
|
29
|
+
function.__module__ = module
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def try_(lazy_func, default=None, exception=Exception):
|
|
33
|
+
try:
|
|
34
|
+
return lazy_func()
|
|
35
|
+
except exception:
|
|
36
|
+
return default
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@contextmanager
|
|
40
|
+
def patch(obj, attr, newvalue):
|
|
41
|
+
had_attr = hasattr(obj, attr)
|
|
42
|
+
orig_value = getattr(obj, attr, None)
|
|
43
|
+
setattr(obj, attr, newvalue)
|
|
44
|
+
try:
|
|
45
|
+
yield
|
|
46
|
+
finally:
|
|
47
|
+
if had_attr:
|
|
48
|
+
setattr(obj, attr, orig_value)
|
|
49
|
+
else:
|
|
50
|
+
delattr(obj, attr)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _as_str(value) -> str:
|
|
54
|
+
if isinstance(value, (Number, str)):
|
|
55
|
+
return str(value)
|
|
56
|
+
if isinstance(value, pd.DataFrame):
|
|
57
|
+
return "df"
|
|
58
|
+
name = str(getattr(value, "name", "") or "")
|
|
59
|
+
if name in ("Open", "High", "Low", "Close", "Volume"):
|
|
60
|
+
return name[:1]
|
|
61
|
+
if callable(value):
|
|
62
|
+
name = getattr(value, "__name__", value.__class__.__name__).replace("<lambda>", "λ")
|
|
63
|
+
if len(name) > 10:
|
|
64
|
+
name = name[:9] + "…"
|
|
65
|
+
return name
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _as_list(value) -> list:
|
|
69
|
+
if isinstance(value, Sequence) and not isinstance(value, str):
|
|
70
|
+
return list(value)
|
|
71
|
+
return [value]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_set_legacy_module(try_, "openbacktesting._util")
|
|
75
|
+
_set_legacy_module(patch, "openbacktesting._util")
|
|
76
|
+
_set_legacy_module(_as_str, "openbacktesting._util")
|
|
77
|
+
_set_legacy_module(_as_list, "openbacktesting._util")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Compatibility exports for old type paths; implemented in the indicators subpackage."""
|
|
2
|
+
|
|
3
|
+
from .indicators.api import (
|
|
4
|
+
IndicatorNamespace,
|
|
5
|
+
IndicatorResult,
|
|
6
|
+
Inputs,
|
|
7
|
+
Source,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = ["IndicatorNamespace", "IndicatorResult", "Inputs", "Source"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Compatibility exports for old type paths; implemented in the indicators subpackage."""
|
|
2
|
+
|
|
3
|
+
from .indicators.methods import (
|
|
4
|
+
AberrationResult,
|
|
5
|
+
AccbandsResult,
|
|
6
|
+
AdxResult,
|
|
7
|
+
AdxrResult,
|
|
8
|
+
AmatResult,
|
|
9
|
+
AobvResult,
|
|
10
|
+
AroonResult,
|
|
11
|
+
BbandsResult,
|
|
12
|
+
BrarResult,
|
|
13
|
+
CdlZResult,
|
|
14
|
+
CeResult,
|
|
15
|
+
CkspResult,
|
|
16
|
+
CprResult,
|
|
17
|
+
DmResult,
|
|
18
|
+
DonchianResult,
|
|
19
|
+
DrawdownResult,
|
|
20
|
+
EriResult,
|
|
21
|
+
FisherResult,
|
|
22
|
+
HaResult,
|
|
23
|
+
HiloResult,
|
|
24
|
+
HtPhasorResult,
|
|
25
|
+
HtSineResult,
|
|
26
|
+
HwcResult,
|
|
27
|
+
IchimokuResult,
|
|
28
|
+
KcResult,
|
|
29
|
+
KdjResult,
|
|
30
|
+
KstResult,
|
|
31
|
+
KvoResult,
|
|
32
|
+
MacdextResult,
|
|
33
|
+
MacdfixResult,
|
|
34
|
+
MacdResult,
|
|
35
|
+
MamaResult,
|
|
36
|
+
MmarResult,
|
|
37
|
+
MswResult,
|
|
38
|
+
PpoResult,
|
|
39
|
+
PsarResult,
|
|
40
|
+
PvoResult,
|
|
41
|
+
QqeResult,
|
|
42
|
+
RainbowResult,
|
|
43
|
+
RvgiResult,
|
|
44
|
+
SmiResult,
|
|
45
|
+
SqueezeProResult,
|
|
46
|
+
SqueezeResult,
|
|
47
|
+
StcResult,
|
|
48
|
+
StochfResult,
|
|
49
|
+
StochResult,
|
|
50
|
+
StochrsiResult,
|
|
51
|
+
SupertrendResult,
|
|
52
|
+
TdSeqResult,
|
|
53
|
+
TechnicalAnalysis,
|
|
54
|
+
ThermoResult,
|
|
55
|
+
TrixhResult,
|
|
56
|
+
TrixResult,
|
|
57
|
+
TsignalsResult,
|
|
58
|
+
TsiResult,
|
|
59
|
+
VortexResult,
|
|
60
|
+
VwmacdResult,
|
|
61
|
+
XsignalsResult,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
__all__ = [
|
|
65
|
+
"AberrationResult",
|
|
66
|
+
"AccbandsResult",
|
|
67
|
+
"AdxResult",
|
|
68
|
+
"AdxrResult",
|
|
69
|
+
"AmatResult",
|
|
70
|
+
"AobvResult",
|
|
71
|
+
"AroonResult",
|
|
72
|
+
"BbandsResult",
|
|
73
|
+
"BrarResult",
|
|
74
|
+
"CdlZResult",
|
|
75
|
+
"CeResult",
|
|
76
|
+
"CkspResult",
|
|
77
|
+
"CprResult",
|
|
78
|
+
"DmResult",
|
|
79
|
+
"DonchianResult",
|
|
80
|
+
"DrawdownResult",
|
|
81
|
+
"EriResult",
|
|
82
|
+
"FisherResult",
|
|
83
|
+
"HaResult",
|
|
84
|
+
"HiloResult",
|
|
85
|
+
"HtPhasorResult",
|
|
86
|
+
"HtSineResult",
|
|
87
|
+
"HwcResult",
|
|
88
|
+
"IchimokuResult",
|
|
89
|
+
"KcResult",
|
|
90
|
+
"KdjResult",
|
|
91
|
+
"KstResult",
|
|
92
|
+
"KvoResult",
|
|
93
|
+
"MacdResult",
|
|
94
|
+
"MacdextResult",
|
|
95
|
+
"MacdfixResult",
|
|
96
|
+
"MamaResult",
|
|
97
|
+
"MmarResult",
|
|
98
|
+
"MswResult",
|
|
99
|
+
"PpoResult",
|
|
100
|
+
"PsarResult",
|
|
101
|
+
"PvoResult",
|
|
102
|
+
"QqeResult",
|
|
103
|
+
"RainbowResult",
|
|
104
|
+
"RvgiResult",
|
|
105
|
+
"SmiResult",
|
|
106
|
+
"SqueezeProResult",
|
|
107
|
+
"SqueezeResult",
|
|
108
|
+
"StcResult",
|
|
109
|
+
"StochResult",
|
|
110
|
+
"StochfResult",
|
|
111
|
+
"StochrsiResult",
|
|
112
|
+
"SupertrendResult",
|
|
113
|
+
"TdSeqResult",
|
|
114
|
+
"TechnicalAnalysis",
|
|
115
|
+
"ThermoResult",
|
|
116
|
+
"TrixResult",
|
|
117
|
+
"TrixhResult",
|
|
118
|
+
"TsiResult",
|
|
119
|
+
"TsignalsResult",
|
|
120
|
+
"VortexResult",
|
|
121
|
+
"VwmacdResult",
|
|
122
|
+
"XsignalsResult",
|
|
123
|
+
]
|