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.
Files changed (350) hide show
  1. openbacktesting/__init__.py +85 -0
  2. openbacktesting/aggregation.py +8 -0
  3. openbacktesting/broker/__init__.py +54 -0
  4. openbacktesting/broker/commission.py +57 -0
  5. openbacktesting/broker/core.py +1122 -0
  6. openbacktesting/broker/execution.py +62 -0
  7. openbacktesting/broker/instrument.py +106 -0
  8. openbacktesting/broker/ledger.py +60 -0
  9. openbacktesting/broker/models.py +236 -0
  10. openbacktesting/broker/observations.py +99 -0
  11. openbacktesting/broker/requests.py +192 -0
  12. openbacktesting/broker/sizing.py +76 -0
  13. openbacktesting/colors.py +175 -0
  14. openbacktesting/data/__init__.py +20 -0
  15. openbacktesting/data/aggregation.py +39 -0
  16. openbacktesting/data/context.py +30 -0
  17. openbacktesting/data/core.py +218 -0
  18. openbacktesting/data/generators.py +54 -0
  19. openbacktesting/data/strategy_data.py +103 -0
  20. openbacktesting/data/timeframe.py +69 -0
  21. openbacktesting/engine.py +34 -0
  22. openbacktesting/helpers.py +77 -0
  23. openbacktesting/indicator_api.py +10 -0
  24. openbacktesting/indicator_methods.py +123 -0
  25. openbacktesting/indicator_types.py +19 -0
  26. openbacktesting/indicators/__init__.py +7 -0
  27. openbacktesting/indicators/api.py +97 -0
  28. openbacktesting/indicators/calculus.py +75 -0
  29. openbacktesting/indicators/candles/__init__.py +0 -0
  30. openbacktesting/indicators/candles/cdl_doji.py +17 -0
  31. openbacktesting/indicators/candles/cdl_engulfing.py +14 -0
  32. openbacktesting/indicators/candles/cdl_eveningstar.py +15 -0
  33. openbacktesting/indicators/candles/cdl_hammer.py +14 -0
  34. openbacktesting/indicators/candles/cdl_harami.py +14 -0
  35. openbacktesting/indicators/candles/cdl_inside.py +14 -0
  36. openbacktesting/indicators/candles/cdl_marubozu.py +14 -0
  37. openbacktesting/indicators/candles/cdl_morningstar.py +15 -0
  38. openbacktesting/indicators/candles/cdl_shootingstar.py +14 -0
  39. openbacktesting/indicators/candles/cdl_z.py +28 -0
  40. openbacktesting/indicators/candles/ha.py +19 -0
  41. openbacktesting/indicators/catalog.json +8348 -0
  42. openbacktesting/indicators/common.py +94 -0
  43. openbacktesting/indicators/cycles/__init__.py +0 -0
  44. openbacktesting/indicators/cycles/dsp.py +10 -0
  45. openbacktesting/indicators/cycles/ebsw.py +10 -0
  46. openbacktesting/indicators/cycles/ht_dcperiod.py +10 -0
  47. openbacktesting/indicators/cycles/ht_dcphase.py +10 -0
  48. openbacktesting/indicators/cycles/ht_phasor.py +17 -0
  49. openbacktesting/indicators/cycles/ht_sine.py +17 -0
  50. openbacktesting/indicators/cycles/ht_trendmode.py +10 -0
  51. openbacktesting/indicators/cycles/msw.py +19 -0
  52. openbacktesting/indicators/methods.py +5762 -0
  53. openbacktesting/indicators/momentum/__init__.py +0 -0
  54. openbacktesting/indicators/momentum/ac.py +9 -0
  55. openbacktesting/indicators/momentum/ao.py +10 -0
  56. openbacktesting/indicators/momentum/apo.py +10 -0
  57. openbacktesting/indicators/momentum/bias.py +10 -0
  58. openbacktesting/indicators/momentum/bop.py +10 -0
  59. openbacktesting/indicators/momentum/brar.py +26 -0
  60. openbacktesting/indicators/momentum/cci.py +10 -0
  61. openbacktesting/indicators/momentum/cfo.py +10 -0
  62. openbacktesting/indicators/momentum/cg.py +10 -0
  63. openbacktesting/indicators/momentum/cmo.py +10 -0
  64. openbacktesting/indicators/momentum/coppock.py +10 -0
  65. openbacktesting/indicators/momentum/crsi.py +28 -0
  66. openbacktesting/indicators/momentum/cti.py +10 -0
  67. openbacktesting/indicators/momentum/dm.py +17 -0
  68. openbacktesting/indicators/momentum/er.py +10 -0
  69. openbacktesting/indicators/momentum/eri.py +17 -0
  70. openbacktesting/indicators/momentum/fisher.py +17 -0
  71. openbacktesting/indicators/momentum/fosc.py +10 -0
  72. openbacktesting/indicators/momentum/gator.py +13 -0
  73. openbacktesting/indicators/momentum/ibs.py +7 -0
  74. openbacktesting/indicators/momentum/inertia.py +22 -0
  75. openbacktesting/indicators/momentum/kdj.py +18 -0
  76. openbacktesting/indicators/momentum/kst.py +30 -0
  77. openbacktesting/indicators/momentum/lrsi.py +10 -0
  78. openbacktesting/indicators/momentum/macd.py +27 -0
  79. openbacktesting/indicators/momentum/macdext.py +27 -0
  80. openbacktesting/indicators/momentum/macdfix.py +18 -0
  81. openbacktesting/indicators/momentum/mom.py +10 -0
  82. openbacktesting/indicators/momentum/pgo.py +10 -0
  83. openbacktesting/indicators/momentum/pmo.py +13 -0
  84. openbacktesting/indicators/momentum/po.py +10 -0
  85. openbacktesting/indicators/momentum/ppo.py +20 -0
  86. openbacktesting/indicators/momentum/psl.py +12 -0
  87. openbacktesting/indicators/momentum/pvo.py +20 -0
  88. openbacktesting/indicators/momentum/qqe.py +31 -0
  89. openbacktesting/indicators/momentum/rci.py +20 -0
  90. openbacktesting/indicators/momentum/rmi.py +7 -0
  91. openbacktesting/indicators/momentum/roc.py +10 -0
  92. openbacktesting/indicators/momentum/rocp.py +10 -0
  93. openbacktesting/indicators/momentum/rocr.py +10 -0
  94. openbacktesting/indicators/momentum/rocr100.py +10 -0
  95. openbacktesting/indicators/momentum/rsi.py +10 -0
  96. openbacktesting/indicators/momentum/rsx.py +10 -0
  97. openbacktesting/indicators/momentum/rvgi.py +26 -0
  98. openbacktesting/indicators/momentum/slope.py +17 -0
  99. openbacktesting/indicators/momentum/smc_sweep.py +18 -0
  100. openbacktesting/indicators/momentum/smi.py +21 -0
  101. openbacktesting/indicators/momentum/smi_stochastic.py +15 -0
  102. openbacktesting/indicators/momentum/squeeze.py +47 -0
  103. openbacktesting/indicators/momentum/squeeze_pro.py +53 -0
  104. openbacktesting/indicators/momentum/stc.py +20 -0
  105. openbacktesting/indicators/momentum/stoch.py +19 -0
  106. openbacktesting/indicators/momentum/stochf.py +19 -0
  107. openbacktesting/indicators/momentum/stochrsi.py +19 -0
  108. openbacktesting/indicators/momentum/td_seq.py +17 -0
  109. openbacktesting/indicators/momentum/trix.py +19 -0
  110. openbacktesting/indicators/momentum/trixh.py +20 -0
  111. openbacktesting/indicators/momentum/tsi.py +26 -0
  112. openbacktesting/indicators/momentum/uo.py +22 -0
  113. openbacktesting/indicators/momentum/vwmacd.py +20 -0
  114. openbacktesting/indicators/momentum/wavetrend.py +15 -0
  115. openbacktesting/indicators/momentum/willr.py +10 -0
  116. openbacktesting/indicators/overlap/__init__.py +0 -0
  117. openbacktesting/indicators/overlap/alligator.py +13 -0
  118. openbacktesting/indicators/overlap/alma.py +33 -0
  119. openbacktesting/indicators/overlap/anchored_vwap.py +50 -0
  120. openbacktesting/indicators/overlap/anchored_vwap_bands.py +13 -0
  121. openbacktesting/indicators/overlap/avgprice.py +10 -0
  122. openbacktesting/indicators/overlap/dema.py +10 -0
  123. openbacktesting/indicators/overlap/ema.py +12 -0
  124. openbacktesting/indicators/overlap/frama.py +29 -0
  125. openbacktesting/indicators/overlap/fwma.py +10 -0
  126. openbacktesting/indicators/overlap/hilo.py +26 -0
  127. openbacktesting/indicators/overlap/hl2.py +10 -0
  128. openbacktesting/indicators/overlap/hlc3.py +10 -0
  129. openbacktesting/indicators/overlap/hma.py +10 -0
  130. openbacktesting/indicators/overlap/ht_trendline.py +10 -0
  131. openbacktesting/indicators/overlap/hwma.py +10 -0
  132. openbacktesting/indicators/overlap/ichimoku.py +44 -0
  133. openbacktesting/indicators/overlap/jma.py +10 -0
  134. openbacktesting/indicators/overlap/kama.py +10 -0
  135. openbacktesting/indicators/overlap/linreg.py +15 -0
  136. openbacktesting/indicators/overlap/linregangle.py +10 -0
  137. openbacktesting/indicators/overlap/linregintercept.py +10 -0
  138. openbacktesting/indicators/overlap/linregslope.py +10 -0
  139. openbacktesting/indicators/overlap/ma.py +10 -0
  140. openbacktesting/indicators/overlap/ma_envelopes.py +16 -0
  141. openbacktesting/indicators/overlap/mama.py +17 -0
  142. openbacktesting/indicators/overlap/mavp.py +17 -0
  143. openbacktesting/indicators/overlap/mcgd.py +10 -0
  144. openbacktesting/indicators/overlap/medprice.py +10 -0
  145. openbacktesting/indicators/overlap/midpoint.py +10 -0
  146. openbacktesting/indicators/overlap/midprice.py +10 -0
  147. openbacktesting/indicators/overlap/mmar.py +21 -0
  148. openbacktesting/indicators/overlap/ohlc4.py +10 -0
  149. openbacktesting/indicators/overlap/pwma.py +10 -0
  150. openbacktesting/indicators/overlap/rainbow.py +25 -0
  151. openbacktesting/indicators/overlap/rma.py +10 -0
  152. openbacktesting/indicators/overlap/sinwma.py +10 -0
  153. openbacktesting/indicators/overlap/sma.py +14 -0
  154. openbacktesting/indicators/overlap/ssf.py +10 -0
  155. openbacktesting/indicators/overlap/supertrend.py +21 -0
  156. openbacktesting/indicators/overlap/swma.py +10 -0
  157. openbacktesting/indicators/overlap/t3.py +10 -0
  158. openbacktesting/indicators/overlap/tema.py +10 -0
  159. openbacktesting/indicators/overlap/trima.py +10 -0
  160. openbacktesting/indicators/overlap/tsf.py +10 -0
  161. openbacktesting/indicators/overlap/typprice.py +10 -0
  162. openbacktesting/indicators/overlap/vidya.py +10 -0
  163. openbacktesting/indicators/overlap/vwap.py +10 -0
  164. openbacktesting/indicators/overlap/vwma.py +10 -0
  165. openbacktesting/indicators/overlap/wcp.py +10 -0
  166. openbacktesting/indicators/overlap/wma.py +10 -0
  167. openbacktesting/indicators/overlap/zlma.py +10 -0
  168. openbacktesting/indicators/performance/__init__.py +0 -0
  169. openbacktesting/indicators/performance/drawdown.py +18 -0
  170. openbacktesting/indicators/performance/log_return.py +10 -0
  171. openbacktesting/indicators/performance/percent_return.py +10 -0
  172. openbacktesting/indicators/predicates.py +88 -0
  173. openbacktesting/indicators/registry.py +571 -0
  174. openbacktesting/indicators/runtime.py +290 -0
  175. openbacktesting/indicators/statistics/__init__.py +0 -0
  176. openbacktesting/indicators/statistics/beta.py +10 -0
  177. openbacktesting/indicators/statistics/correl.py +10 -0
  178. openbacktesting/indicators/statistics/entropy.py +10 -0
  179. openbacktesting/indicators/statistics/kurtosis.py +10 -0
  180. openbacktesting/indicators/statistics/mad.py +10 -0
  181. openbacktesting/indicators/statistics/md.py +10 -0
  182. openbacktesting/indicators/statistics/median.py +10 -0
  183. openbacktesting/indicators/statistics/mode.py +48 -0
  184. openbacktesting/indicators/statistics/quantile.py +10 -0
  185. openbacktesting/indicators/statistics/skew.py +10 -0
  186. openbacktesting/indicators/statistics/stderr.py +10 -0
  187. openbacktesting/indicators/statistics/stdev.py +10 -0
  188. openbacktesting/indicators/statistics/variance.py +10 -0
  189. openbacktesting/indicators/statistics/zscore.py +10 -0
  190. openbacktesting/indicators/trend/__init__.py +0 -0
  191. openbacktesting/indicators/trend/adx.py +28 -0
  192. openbacktesting/indicators/trend/adxr.py +28 -0
  193. openbacktesting/indicators/trend/amat.py +19 -0
  194. openbacktesting/indicators/trend/aroon.py +18 -0
  195. openbacktesting/indicators/trend/atr_stop.py +33 -0
  196. openbacktesting/indicators/trend/chop.py +20 -0
  197. openbacktesting/indicators/trend/cksp.py +17 -0
  198. openbacktesting/indicators/trend/cpr.py +57 -0
  199. openbacktesting/indicators/trend/decay.py +10 -0
  200. openbacktesting/indicators/trend/decreasing.py +18 -0
  201. openbacktesting/indicators/trend/dpo.py +12 -0
  202. openbacktesting/indicators/trend/dx.py +19 -0
  203. openbacktesting/indicators/trend/edecay.py +10 -0
  204. openbacktesting/indicators/trend/increasing.py +18 -0
  205. openbacktesting/indicators/trend/long_run.py +10 -0
  206. openbacktesting/indicators/trend/minus_dm.py +10 -0
  207. openbacktesting/indicators/trend/pivot_points.py +89 -0
  208. openbacktesting/indicators/trend/pivothigh.py +23 -0
  209. openbacktesting/indicators/trend/pivotlow.py +23 -0
  210. openbacktesting/indicators/trend/plus_dm.py +10 -0
  211. openbacktesting/indicators/trend/pmax.py +18 -0
  212. openbacktesting/indicators/trend/psar.py +19 -0
  213. openbacktesting/indicators/trend/qstick.py +10 -0
  214. openbacktesting/indicators/trend/sarext.py +35 -0
  215. openbacktesting/indicators/trend/short_run.py +10 -0
  216. openbacktesting/indicators/trend/tsignals.py +26 -0
  217. openbacktesting/indicators/trend/ttm_trend.py +16 -0
  218. openbacktesting/indicators/trend/vhf.py +10 -0
  219. openbacktesting/indicators/trend/volatility_stop.py +33 -0
  220. openbacktesting/indicators/trend/vortex.py +17 -0
  221. openbacktesting/indicators/trend/xsignals.py +29 -0
  222. openbacktesting/indicators/types.py +344 -0
  223. openbacktesting/indicators/utility/__init__.py +0 -0
  224. openbacktesting/indicators/utility/barssince.py +21 -0
  225. openbacktesting/indicators/utility/change.py +7 -0
  226. openbacktesting/indicators/utility/cross.py +12 -0
  227. openbacktesting/indicators/utility/crossover.py +11 -0
  228. openbacktesting/indicators/utility/crossunder.py +11 -0
  229. openbacktesting/indicators/utility/cum.py +7 -0
  230. openbacktesting/indicators/utility/falling.py +10 -0
  231. openbacktesting/indicators/utility/highest.py +7 -0
  232. openbacktesting/indicators/utility/highestbars.py +29 -0
  233. openbacktesting/indicators/utility/lowest.py +7 -0
  234. openbacktesting/indicators/utility/lowestbars.py +29 -0
  235. openbacktesting/indicators/utility/percentile_linear_interpolation.py +12 -0
  236. openbacktesting/indicators/utility/percentile_nearest_rank.py +16 -0
  237. openbacktesting/indicators/utility/percentrank.py +7 -0
  238. openbacktesting/indicators/utility/range.py +8 -0
  239. openbacktesting/indicators/utility/rising.py +10 -0
  240. openbacktesting/indicators/utility/rolling_count.py +12 -0
  241. openbacktesting/indicators/utility/rolling_sum.py +7 -0
  242. openbacktesting/indicators/utility/running_max.py +7 -0
  243. openbacktesting/indicators/utility/running_min.py +7 -0
  244. openbacktesting/indicators/utility/shift.py +7 -0
  245. openbacktesting/indicators/utility/valuewhen.py +25 -0
  246. openbacktesting/indicators/volatility/__init__.py +0 -0
  247. openbacktesting/indicators/volatility/aberration.py +21 -0
  248. openbacktesting/indicators/volatility/accbands.py +20 -0
  249. openbacktesting/indicators/volatility/adr.py +7 -0
  250. openbacktesting/indicators/volatility/atr.py +12 -0
  251. openbacktesting/indicators/volatility/avolume.py +10 -0
  252. openbacktesting/indicators/volatility/bbands.py +20 -0
  253. openbacktesting/indicators/volatility/ce.py +25 -0
  254. openbacktesting/indicators/volatility/cvi.py +10 -0
  255. openbacktesting/indicators/volatility/donchian.py +20 -0
  256. openbacktesting/indicators/volatility/hvol.py +10 -0
  257. openbacktesting/indicators/volatility/hwc.py +27 -0
  258. openbacktesting/indicators/volatility/kc.py +27 -0
  259. openbacktesting/indicators/volatility/massi.py +10 -0
  260. openbacktesting/indicators/volatility/natr.py +19 -0
  261. openbacktesting/indicators/volatility/pdist.py +10 -0
  262. openbacktesting/indicators/volatility/rvi.py +21 -0
  263. openbacktesting/indicators/volatility/starc.py +11 -0
  264. openbacktesting/indicators/volatility/thermo.py +28 -0
  265. openbacktesting/indicators/volatility/true_range.py +31 -0
  266. openbacktesting/indicators/volatility/ui.py +10 -0
  267. openbacktesting/indicators/volume/__init__.py +0 -0
  268. openbacktesting/indicators/volume/ad.py +10 -0
  269. openbacktesting/indicators/volume/adosc.py +19 -0
  270. openbacktesting/indicators/volume/aobv.py +31 -0
  271. openbacktesting/indicators/volume/cmf.py +12 -0
  272. openbacktesting/indicators/volume/efi.py +12 -0
  273. openbacktesting/indicators/volume/emv.py +10 -0
  274. openbacktesting/indicators/volume/eom.py +19 -0
  275. openbacktesting/indicators/volume/iii.py +9 -0
  276. openbacktesting/indicators/volume/kvo.py +28 -0
  277. openbacktesting/indicators/volume/marketfi.py +10 -0
  278. openbacktesting/indicators/volume/mfi.py +12 -0
  279. openbacktesting/indicators/volume/nvi.py +26 -0
  280. openbacktesting/indicators/volume/obv.py +10 -0
  281. openbacktesting/indicators/volume/pvi.py +26 -0
  282. openbacktesting/indicators/volume/pvol.py +10 -0
  283. openbacktesting/indicators/volume/pvr.py +10 -0
  284. openbacktesting/indicators/volume/pvt.py +10 -0
  285. openbacktesting/indicators/volume/rvol.py +8 -0
  286. openbacktesting/indicators/volume/vfi.py +18 -0
  287. openbacktesting/indicators/volume/vosc.py +10 -0
  288. openbacktesting/indicators/volume/wad.py +10 -0
  289. openbacktesting/indicators/volume/wvad.py +9 -0
  290. openbacktesting/inputs.py +51 -0
  291. openbacktesting/lib.py +31 -0
  292. openbacktesting/logs.py +129 -0
  293. openbacktesting/optimization.py +8 -0
  294. openbacktesting/optimizer/__init__.py +25 -0
  295. openbacktesting/optimizer/_semantics.py +155 -0
  296. openbacktesting/optimizer/identity.py +45 -0
  297. openbacktesting/optimizer/models.py +92 -0
  298. openbacktesting/optimizer/plan.py +129 -0
  299. openbacktesting/optimizer/portfolio-result.schema.json +4984 -0
  300. openbacktesting/optimizer/query.py +257 -0
  301. openbacktesting/optimizer/runner.py +98 -0
  302. openbacktesting/optimizer/storage.py +261 -0
  303. openbacktesting/optimizer/validation.py +64 -0
  304. openbacktesting/parallel.py +9 -0
  305. openbacktesting/plotting/__init__.py +23 -0
  306. openbacktesting/plotting/collector.py +509 -0
  307. openbacktesting/plotting/encoding.py +102 -0
  308. openbacktesting/plotting/types.py +92 -0
  309. openbacktesting/portfolio/__init__.py +15 -0
  310. openbacktesting/portfolio/analysis.py +245 -0
  311. openbacktesting/portfolio/builder.py +381 -0
  312. openbacktesting/portfolio/catalog.json +1404 -0
  313. openbacktesting/portfolio/helpers.py +92 -0
  314. openbacktesting/portfolio/metrics.py +186 -0
  315. openbacktesting/portfolio/models.py +63 -0
  316. openbacktesting/portfolio/periods.py +164 -0
  317. openbacktesting/portfolio/rates.py +14 -0
  318. openbacktesting/portfolio/snapshot.py +111 -0
  319. openbacktesting/portfolio/stats.py +272 -0
  320. openbacktesting/portfolio/trades.py +136 -0
  321. openbacktesting/portfolio/trading.py +215 -0
  322. openbacktesting/py.typed +0 -0
  323. openbacktesting/runtime.py +330 -0
  324. openbacktesting/simulation/__init__.py +17 -0
  325. openbacktesting/simulation/collection.py +13 -0
  326. openbacktesting/simulation/config.py +241 -0
  327. openbacktesting/simulation/core.py +528 -0
  328. openbacktesting/simulation/optimization.py +326 -0
  329. openbacktesting/simulation/parallel.py +145 -0
  330. openbacktesting/simulation/variants.py +107 -0
  331. openbacktesting/strategy/__init__.py +19 -0
  332. openbacktesting/strategy/bar_state.py +14 -0
  333. openbacktesting/strategy/base.py +810 -0
  334. openbacktesting/strategy/declaration.py +90 -0
  335. openbacktesting/strategy/helpers.py +198 -0
  336. openbacktesting/strategy/inputs.py +460 -0
  337. openbacktesting/strategy/sizing.py +80 -0
  338. openbacktesting/strategy/templates.py +160 -0
  339. openbacktesting/strategy_data.py +10 -0
  340. openbacktesting/test/BTCUSD.csv +157 -0
  341. openbacktesting/test/EURUSD.csv +5001 -0
  342. openbacktesting/test/GOOG.csv +2149 -0
  343. openbacktesting/test/__init__.py +29 -0
  344. openbacktesting/test/__main__.py +11 -0
  345. openbacktesting/test/_test.py +1312 -0
  346. openbacktesting/util.py +31 -0
  347. openbacktesting-0.2.0.dist-info/METADATA +151 -0
  348. openbacktesting-0.2.0.dist-info/RECORD +350 -0
  349. openbacktesting-0.2.0.dist-info/WHEEL +4 -0
  350. openbacktesting-0.2.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,330 @@
1
+ """Consume successfully loaded strategy modules; discovery needs no data, and the host injects chart context for execution."""
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import asdict
5
+ from importlib.metadata import version
6
+ from inspect import isabstract
7
+
8
+ import pandas as pd
9
+
10
+ from openbacktesting.broker.instrument import InstrumentSpec
11
+ from openbacktesting.data.context import DataContext
12
+ from openbacktesting.indicators.runtime import DEFAULT_CACHE_BYTES
13
+ from openbacktesting.logs import RunLogs
14
+ from openbacktesting.optimizer.query import ExperimentResult
15
+ from openbacktesting.plotting.types import PlottingLimits
16
+ from openbacktesting.simulation.collection import DEFAULT_COLLECTION, CollectionPolicy
17
+ from openbacktesting.simulation.core import Backtest
18
+ from openbacktesting.strategy.base import Strategy
19
+ from openbacktesting.strategy.declaration import declaration
20
+ from openbacktesting.strategy.inputs import describe as describe_inputs
21
+ from openbacktesting.strategy.inputs import resolve_request
22
+
23
+
24
+ def discover(namespace: Mapping) -> type[Strategy]:
25
+ """Discover the single concrete strategy directly decorated in this module; deduplicate aliases and exclude imported and inherited entry points.
26
+
27
+ Args:
28
+ namespace: Successfully loaded module namespace containing a nonempty string __name__.
29
+
30
+ Returns:
31
+ type[Strategy]: Original strategy class, without instance construction.
32
+
33
+ Raises:
34
+ TypeError: Invalid namespace or declaration.
35
+ ValueError: No entry point or multiple entry points.
36
+ """
37
+ if not isinstance(namespace, Mapping):
38
+ raise TypeError("namespace must be a module mapping")
39
+ name = namespace.get("__name__")
40
+ if not isinstance(name, str) or not name:
41
+ raise TypeError("namespace must contain a nonempty __name__")
42
+ candidates = {}
43
+ for value in namespace.values():
44
+ if (
45
+ isinstance(value, type)
46
+ and issubclass(value, Strategy)
47
+ and value.__module__ == name
48
+ and not isabstract(value)
49
+ and declaration(value) is not None
50
+ ):
51
+ candidates[id(value)] = value
52
+ if len(candidates) != 1:
53
+ names = sorted(f"{cls.__module__}:{cls.__qualname__}" for cls in candidates.values())
54
+ raise ValueError(f"The module must contain exactly one @backtest strategy; candidates: {names}")
55
+ return next(iter(candidates.values()))
56
+
57
+
58
+ def describe(namespace: Mapping) -> dict:
59
+ """Export inputs and complete default backtest settings without market data or executing the strategy lifecycle.
60
+
61
+ The returned backtest description is independent of input schemaHash; modifying copies does not alter class declarations.
62
+ """
63
+ cls = discover(namespace)
64
+ settings = declaration(cls)
65
+ return describe_inputs(cls) | {
66
+ "backtest": {"schemaVersion": 2, "settings": settings.snapshot()}
67
+ }
68
+
69
+
70
+ def run(
71
+ namespace: Mapping,
72
+ request: Mapping | None = None,
73
+ *,
74
+ data: pd.DataFrame,
75
+ context: DataContext,
76
+ instrument: InstrumentSpec,
77
+ settings: Mapping | None = None,
78
+ options: Mapping | None = None,
79
+ logs: RunLogs | None = None,
80
+ ) -> pd.Series:
81
+ """Run a new session for this chart and return existing results plus backtest_snapshot.
82
+
83
+ Args:
84
+ namespace: Module successfully loaded by the host for this run; bt-only exports are no longer accepted.
85
+ request: Existing title-based input request; None uses defaults.
86
+ data: Current chart OHLC with a timezone-aware DatetimeIndex.
87
+ context: Actual timeframe and shared timezone for charts, strategy calendars, and statistics.
88
+ Convert the internal index before strategy construction, preserving inputs and absolute instants without resampling.
89
+ Changing timezone requires a new run; local-time conditions and statistics grouped by day may change.
90
+ instrument: Chart instrument specifications, without inference from market data.
91
+ settings: Sparse overrides of flat settings; enum fields accept corresponding members or exact strings.
92
+ Changing types requires supplying a value too; default_qty_value=None clears the default quantity.
93
+ options: cache budget, plotting, and plotting_limits are host runtime options.
94
+ logs: Optional host log session; get_logs works after success or exceptions, and sequential reuse clears old records.
95
+
96
+ No source loading, cached old instances, or declaration changes; exceptions propagate unchanged.
97
+ """
98
+ if logs is not None and not isinstance(logs, RunLogs):
99
+ raise TypeError("logs must be RunLogs or None")
100
+ session = RunLogs() if logs is None else logs
101
+ with session._capture():
102
+ return _run(namespace, request, data, context, instrument, settings, options, session)
103
+
104
+
105
+ def _run(
106
+ namespace,
107
+ request,
108
+ data,
109
+ context,
110
+ instrument,
111
+ settings,
112
+ options,
113
+ session,
114
+ collection=DEFAULT_COLLECTION,
115
+ ):
116
+ cls = discover(namespace)
117
+ resolved = declaration(cls).resolve(settings)
118
+ if not isinstance(context, DataContext):
119
+ raise TypeError("context must be DataContext")
120
+ if not isinstance(instrument, InstrumentSpec):
121
+ raise TypeError("instrument must be InstrumentSpec")
122
+ if options is None:
123
+ options = {}
124
+ if not isinstance(options, Mapping):
125
+ raise TypeError("options must be a mapping or None")
126
+ defaults = {
127
+ "indicator_cache_bytes": DEFAULT_CACHE_BYTES,
128
+ "plotting": True,
129
+ "plotting_limits": PlottingLimits(),
130
+ }
131
+ unknown = set(options).difference(defaults)
132
+ if unknown:
133
+ raise ValueError(f"Unknown runtime option: {list(unknown)!r}")
134
+ effective_options = defaults | dict(options)
135
+ if effective_options["plotting_limits"] is None:
136
+ effective_options["plotting_limits"] = PlottingLimits()
137
+ # Digest and value errors must surface before constructing any strategy, preserving existing title-based entry semantics.
138
+ if request is not None:
139
+ resolve_request(cls, request)
140
+ bt = Backtest(
141
+ data,
142
+ cls,
143
+ context=context,
144
+ instrument=instrument,
145
+ _collection=collection,
146
+ **resolved.kwargs(),
147
+ **effective_options,
148
+ )
149
+ try:
150
+ result = bt.run() if request is None else bt.run_inputs(request)
151
+ finally:
152
+ session._collector = bt._log_collector
153
+ result.attrs["backtest_snapshot"] = {
154
+ "schemaVersion": 2,
155
+ "strategy": {"module": cls.__module__, "qualname": cls.__qualname__},
156
+ "settings": resolved.snapshot(),
157
+ "context": context.snapshot(),
158
+ "instrument": asdict(instrument),
159
+ "options": effective_options
160
+ | {"plotting_limits": asdict(effective_options["plotting_limits"])},
161
+ "engineVersion": version("openbacktesting"),
162
+ }
163
+ from openbacktesting.portfolio.snapshot import refresh_snapshot
164
+
165
+ refresh_snapshot(result)
166
+ return result
167
+
168
+
169
+ def optimize(
170
+ namespace: Mapping,
171
+ search: Mapping,
172
+ *,
173
+ path,
174
+ identity,
175
+ data: pd.DataFrame,
176
+ context: DataContext,
177
+ instrument: InstrumentSpec,
178
+ request=None,
179
+ settings=None,
180
+ constraint=None,
181
+ constraint_id=None,
182
+ cancel=None,
183
+ on_progress=None,
184
+ ) -> ExperimentResult:
185
+ """Exhaustively search selected inputs sequentially, committing each complete Portfolio to a new local result database.
186
+
187
+ Unselected values come from the current request; retain only Portfolio without collecting logs/plotting.
188
+ identity is a host-supplied ExecutionIdentity; the framework computes data/configuration identities.
189
+ constraint uses complete wire values and requires constraint_id; cancel is a no-argument callable.
190
+ Return a read-only ExperimentResult; record strategy-trial failures and continue, but stop on storage failures.
191
+ """
192
+ return _optimize(
193
+ namespace,
194
+ search,
195
+ path,
196
+ identity,
197
+ data,
198
+ context,
199
+ instrument,
200
+ request,
201
+ settings,
202
+ constraint,
203
+ constraint_id,
204
+ cancel,
205
+ on_progress,
206
+ None,
207
+ )
208
+
209
+
210
+ def resume_optimization(
211
+ namespace: Mapping,
212
+ *,
213
+ path,
214
+ experiment_id: str,
215
+ identity,
216
+ data: pd.DataFrame,
217
+ context: DataContext,
218
+ instrument: InstrumentSpec,
219
+ request=None,
220
+ settings=None,
221
+ constraint=None,
222
+ constraint_id=None,
223
+ cancel=None,
224
+ on_progress=None,
225
+ ) -> ExperimentResult:
226
+ """Resume unfinished trials after checking current declarations, inputs, and execution identity; failed trials are not automatically retried."""
227
+ from openbacktesting.optimizer.models import Range, Values
228
+ from openbacktesting.optimizer.query import open_result
229
+
230
+ source = open_result(path, experiment_id).info()["plan"]
231
+ search = {}
232
+ for dimension in source["dimensions"]:
233
+ expression = dimension["expression"]
234
+ search[dimension["title"]] = (
235
+ Range(**expression["range"]) if "range" in expression else Values(expression["values"])
236
+ )
237
+ return _optimize(
238
+ namespace,
239
+ search,
240
+ path,
241
+ identity,
242
+ data,
243
+ context,
244
+ instrument,
245
+ request,
246
+ settings,
247
+ constraint,
248
+ constraint_id,
249
+ cancel,
250
+ on_progress,
251
+ experiment_id,
252
+ )
253
+
254
+
255
+ def _optimize(
256
+ namespace,
257
+ search,
258
+ path,
259
+ identity,
260
+ data,
261
+ context,
262
+ instrument,
263
+ request,
264
+ settings,
265
+ constraint,
266
+ constraint_id,
267
+ cancel,
268
+ on_progress,
269
+ experiment_id,
270
+ ):
271
+ from copy import deepcopy
272
+
273
+ from openbacktesting.optimizer.identity import execution_identity
274
+ from openbacktesting.optimizer.models import _ExportError
275
+ from openbacktesting.optimizer.plan import plan
276
+ from openbacktesting.optimizer.runner import execute
277
+ from openbacktesting.portfolio.snapshot import export_result
278
+
279
+ cls = discover(namespace)
280
+ resolved = declaration(cls).resolve(settings)
281
+ if not isinstance(context, DataContext) or not isinstance(instrument, InstrumentSpec):
282
+ raise TypeError("optimizer: DataContext and InstrumentSpec must be supplied")
283
+ frozen = data.copy(deep=True)
284
+ run_settings = resolved.snapshot()
285
+ backtest = {
286
+ "schemaVersion": 2,
287
+ "settings": run_settings,
288
+ "context": context.snapshot(),
289
+ "instrument": asdict(instrument),
290
+ "engineVersion": version("openbacktesting"),
291
+ }
292
+ execution = execution_identity(frozen, identity, backtest)
293
+ plan_value = plan(
294
+ cls, search, base_inputs=request, execution=execution, constraint_id=constraint_id
295
+ )
296
+ # Construction validation does not execute strategies; reject invalid market data before creating a database or running the first trial.
297
+ Backtest(
298
+ frozen, cls, context=context, instrument=instrument, **resolved.kwargs(), plotting=False
299
+ )
300
+ namespace = dict(namespace)
301
+ collection = CollectionPolicy(portfolio=True, logs=False, plotting=False)
302
+
303
+ def run_one(inputs_request):
304
+ session = RunLogs()
305
+ with session._capture():
306
+ result = _run(
307
+ namespace,
308
+ inputs_request,
309
+ frozen,
310
+ context,
311
+ instrument,
312
+ deepcopy(run_settings),
313
+ {"plotting": False},
314
+ session,
315
+ collection,
316
+ )
317
+ try:
318
+ return export_result(result)
319
+ except Exception as error:
320
+ raise _ExportError(str(error)) from error
321
+
322
+ return execute(
323
+ plan_value,
324
+ run_one,
325
+ path,
326
+ experiment_id=experiment_id,
327
+ constraint=constraint,
328
+ cancel=cancel,
329
+ on_progress=on_progress,
330
+ )
@@ -0,0 +1,17 @@
1
+ """Lazy class exports for the simulation package; implemented in simulation.core."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from openbacktesting.simulation.core import Backtest as Backtest
7
+
8
+ __all__ = ["Backtest"]
9
+
10
+
11
+ def __getattr__(name):
12
+ if name == "Backtest":
13
+ from openbacktesting.simulation.core import Backtest
14
+
15
+ globals()[name] = Backtest
16
+ return Backtest
17
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,13 @@
1
+ """Internal run-collection policy; defaults match ordinary backtest behavior."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class CollectionPolicy:
8
+ portfolio: bool = True
9
+ logs: bool = True
10
+ plotting: bool = True
11
+
12
+
13
+ DEFAULT_COLLECTION = CollectionPolicy()
@@ -0,0 +1,241 @@
1
+ """Exportable default backtest settings independent of strategies, hosts, and execution lifecycles."""
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass, fields, replace
5
+ from enum import Enum
6
+
7
+ from openbacktesting.broker.commission import Commission
8
+ from openbacktesting.broker.execution import Slippage
9
+ from openbacktesting.broker.models import finite
10
+ from openbacktesting.portfolio.rates import validate_risk_free_rate
11
+
12
+
13
+ class CommissionType(Enum):
14
+ """Author-facing commission type; PERCENT uses percentage points, and other members use account amounts."""
15
+
16
+ PERCENT = "percent"
17
+ CASH_PER_ORDER = "cash_per_order"
18
+ CASH_PER_CONTRACT = "cash_per_contract"
19
+
20
+
21
+ class QuantityType(Enum):
22
+ """Default order size: Fixed asset quantity or a percentage of equity at request time."""
23
+
24
+ FIXED = "fixed"
25
+ PERCENT_OF_EQUITY = "percent_of_equity"
26
+
27
+
28
+ class SlippageType(Enum):
29
+ """Slippage distance: Fixed price units or basis points of price."""
30
+
31
+ FIXED = "fixed"
32
+ BPS = "bps"
33
+
34
+
35
+ class ExecutionTiming(Enum):
36
+ """Execution timing: Process no earlier than the next bar, or process new unconditional market orders at this bar's close."""
37
+
38
+ NEXT_OPEN = "next_open"
39
+ SAME_CLOSE = "same_close"
40
+
41
+
42
+ class EndPolicy(Enum):
43
+ """End policy: Retain marked positions or liquidate actual positions at the final close."""
44
+
45
+ MARK_TO_MARKET = "mark_to_market"
46
+ CLOSE_AT_LAST_CLOSE = "close_at_last_close"
47
+
48
+
49
+ _ENUM_FIELDS = {
50
+ "commission_type": CommissionType,
51
+ "default_qty_type": QuantityType,
52
+ "slippage_type": SlippageType,
53
+ "execution_timing": ExecutionTiming,
54
+ "end_policy": EndPolicy,
55
+ }
56
+ _PAIRED_FIELDS = (
57
+ ("commission_type", "commission_value"),
58
+ ("default_qty_type", "default_qty_value"),
59
+ ("slippage_type", "slippage_value"),
60
+ )
61
+
62
+
63
+ def _decode_enums(values: Mapping) -> dict:
64
+ """Convert exact strings to corresponding enum members only at the host boundary."""
65
+ result = dict(values)
66
+ for name, kind in _ENUM_FIELDS.items():
67
+ if name not in result:
68
+ continue
69
+ value = result[name]
70
+ if type(value) is str:
71
+ try:
72
+ result[name] = kind(value)
73
+ except ValueError:
74
+ choices = ", ".join(member.value for member in kind)
75
+ raise ValueError(f"{name} must be one of: {choices}") from None
76
+ elif not isinstance(value, kind):
77
+ raise TypeError(f"{name} must be {kind.__name__} member or corresponding string")
78
+ return result
79
+
80
+
81
+ @dataclass(frozen=True)
82
+ class DefaultOrderSize:
83
+ """Request budget when ordinary orders omit qty; does not represent a target position or filled notional."""
84
+
85
+ mode: str
86
+ value: float
87
+
88
+ def __post_init__(self):
89
+ if self.mode not in ("fixed", "equity_fraction"):
90
+ raise ValueError("mode must be fixed or equity_fraction")
91
+ object.__setattr__(self, "value", finite(self.value, "value", positive=True))
92
+ if self.mode == "equity_fraction" and self.value > 1:
93
+ raise ValueError("equity_fraction must be in (0, 1]")
94
+
95
+ def snapshot(self) -> dict:
96
+ """Export independent mode and value."""
97
+ return {"mode": self.mode, "value": self.value}
98
+
99
+ @classmethod
100
+ def from_snapshot(cls, value: Mapping) -> "DefaultOrderSize":
101
+ """Strictly restore complete configuration without filling missing fields."""
102
+ if not isinstance(value, Mapping):
103
+ raise TypeError("DefaultOrderSize snapshot must be a mapping")
104
+ if set(value) != {"mode", "value"}:
105
+ raise ValueError("DefaultOrderSize snapshot has missing or unknown fields")
106
+ return cls(value["mode"], value["value"])
107
+
108
+
109
+ def validate_order_size(value: DefaultOrderSize | None) -> DefaultOrderSize | None:
110
+ """Accept only validated immutable models or explicit clearing."""
111
+ if value is not None and not isinstance(value, DefaultOrderSize):
112
+ raise TypeError("default_order_size must be DefaultOrderSize or None")
113
+ return value
114
+
115
+
116
+ @dataclass(frozen=True)
117
+ class BacktestSettings:
118
+ """Flat immutable author-facing settings; validate enums and numbers after decoding at the host boundary."""
119
+
120
+ cash: float = 10_000
121
+ spread: float = 0.0
122
+ commission_type: CommissionType = CommissionType.PERCENT
123
+ commission_value: float = 0.0
124
+ risk_free_rate: float = 0.0
125
+ default_qty_type: QuantityType = QuantityType.FIXED
126
+ default_qty_value: float | None = None
127
+ margin: float = 1.0
128
+ execution_timing: ExecutionTiming = ExecutionTiming.NEXT_OPEN
129
+ slippage_type: SlippageType = SlippageType.BPS
130
+ slippage_value: float = 0.0
131
+ allow_short: bool = True
132
+ max_gross_exposure: float | None = None
133
+ hedging: bool = False
134
+ exclusive_orders: bool = False
135
+ end_policy: EndPolicy = EndPolicy.MARK_TO_MARKET
136
+
137
+ def __post_init__(self):
138
+ for name, kind in _ENUM_FIELDS.items():
139
+ if not isinstance(getattr(self, name), kind):
140
+ raise TypeError(f"{name} must be {kind.__name__} member")
141
+ object.__setattr__(self, "risk_free_rate", validate_risk_free_rate(self.risk_free_rate))
142
+ for name in ("cash", "margin"):
143
+ object.__setattr__(self, name, finite(getattr(self, name), name, positive=True))
144
+ spread = finite(self.spread, "spread")
145
+ object.__setattr__(self, "spread", spread)
146
+ if self.margin > 1:
147
+ raise ValueError("margin must be in (0,1]")
148
+ if not 0 <= spread < 1:
149
+ raise ValueError("spread must be in [0,1)")
150
+ if self.max_gross_exposure is not None:
151
+ object.__setattr__(
152
+ self,
153
+ "max_gross_exposure",
154
+ finite(self.max_gross_exposure, "max_gross_exposure", positive=True),
155
+ )
156
+ for name in ("allow_short", "hedging", "exclusive_orders"):
157
+ if type(getattr(self, name)) is not bool:
158
+ raise TypeError(f"{name} must be bool")
159
+ value = finite(self.commission_value, "commission_value")
160
+ if self.commission_type is CommissionType.PERCENT:
161
+ if not -10 <= value < 10:
162
+ raise ValueError("commission_value percentage must be in [-10,10)")
163
+ elif value < 0:
164
+ raise ValueError("commission_value amount must not be negative")
165
+ object.__setattr__(self, "commission_value", value)
166
+ if self.default_qty_value is not None:
167
+ value = finite(self.default_qty_value, "default_qty_value", positive=True)
168
+ if self.default_qty_type is QuantityType.PERCENT_OF_EQUITY and value > 100:
169
+ raise ValueError("default_qty_value equity percentage must be in (0,100]")
170
+ object.__setattr__(self, "default_qty_value", value)
171
+ value = finite(self.slippage_value, "slippage_value")
172
+ if value < 0:
173
+ raise ValueError("slippage_value must not be negative")
174
+ object.__setattr__(self, "slippage_value", value)
175
+
176
+ def _values(self) -> dict:
177
+ return {field.name: getattr(self, field.name) for field in fields(self)}
178
+
179
+ def kwargs(self) -> dict:
180
+ """Adapt to low-level Backtest arguments, separately from flat snapshots and the override allowlist."""
181
+ result = self._values()
182
+ for type_name, value_name in _PAIRED_FIELDS:
183
+ del result[type_name], result[value_name]
184
+ if self.commission_type is CommissionType.PERCENT:
185
+ commission = Commission(relative=self.commission_value / 100)
186
+ elif self.commission_type is CommissionType.CASH_PER_ORDER:
187
+ commission = Commission(fixed=self.commission_value)
188
+ else:
189
+ commission = Commission(per_unit=self.commission_value)
190
+ size = None
191
+ if self.default_qty_value is not None:
192
+ size = (
193
+ DefaultOrderSize("fixed", self.default_qty_value)
194
+ if self.default_qty_type is QuantityType.FIXED
195
+ else DefaultOrderSize("equity_fraction", self.default_qty_value / 100)
196
+ )
197
+ return result | {
198
+ "commission": commission,
199
+ "default_order_size": size,
200
+ "slippage": Slippage(self.slippage_type.value, self.slippage_value),
201
+ "execution_timing": self.execution_timing.value,
202
+ "end_policy": self.end_policy.value,
203
+ }
204
+
205
+ def resolve(self, overrides: Mapping | None) -> "BacktestSettings":
206
+ """Decode sparse host overrides; a changed type must be supplied together with a new value."""
207
+ if overrides is None:
208
+ overrides = {}
209
+ if not isinstance(overrides, Mapping):
210
+ raise TypeError("settings must be a mapping or None")
211
+ unknown = set(overrides).difference(self._values())
212
+ if unknown:
213
+ raise ValueError(f"Unknown backtest setting: {list(unknown)!r}")
214
+ values = _decode_enums(overrides)
215
+ for type_name, value_name in _PAIRED_FIELDS:
216
+ if (
217
+ type_name in values
218
+ and values[type_name] is not getattr(self, type_name)
219
+ and value_name not in values
220
+ ):
221
+ raise ValueError(f"Changing {type_name} requires also supplying {value_name}")
222
+ return replace(self, **values)
223
+
224
+ def snapshot(self) -> dict:
225
+ """Export all flat fields; enums become strings and percentages retain author units."""
226
+ return {
227
+ name: value.value if isinstance(value, Enum) else value
228
+ for name, value in self._values().items()
229
+ }
230
+
231
+ @classmethod
232
+ def from_snapshot(cls, value: Mapping) -> "BacktestSettings":
233
+ """Strictly restore complete JSON settings; sparse fields and Python enums are not accepted."""
234
+ if not isinstance(value, Mapping):
235
+ raise TypeError("Backtest settings snapshot must be a mapping")
236
+ if set(value) != {field.name for field in fields(cls)}:
237
+ raise ValueError("Backtest settings snapshot has missing or unknown fields")
238
+ for name, item in value.items():
239
+ if type(item) not in (str, int, float, bool, type(None)):
240
+ raise TypeError(f"{name} must be a JSON primitive")
241
+ return cls(**_decode_enums(value))