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,10 @@
1
+ """ta.pvt: Computation wrapper for the pinned dependency with canonical outputs."""
2
+
3
+ from pandas_ta_classic.volume.pvt import pvt as _calculate
4
+
5
+ from ..common import named_series
6
+
7
+
8
+ def compute(*, close, volume, drift, talib):
9
+ result = _calculate(close=close, volume=volume, drift=drift, talib=talib)
10
+ return named_series(result, "ta.pvt", "pvt")
@@ -0,0 +1,8 @@
1
+ """ta.rvol: Independent formula with a fixed initialization contract."""
2
+
3
+ from ..calculus import clean, ratio
4
+
5
+
6
+ def compute(*, volume, length):
7
+ volume = clean(volume)
8
+ return ratio(volume, volume.shift(1).rolling(length).mean()).rename("rvol")
@@ -0,0 +1,18 @@
1
+ """ta.vfi: Computation wrapper for the pinned dependency with canonical outputs."""
2
+
3
+ from pandas_ta_classic.volume.vfi import vfi as _calculate
4
+
5
+ from ..common import named_series
6
+
7
+
8
+ def compute(*, close, volume, length, coef, vcoef, mamode, talib):
9
+ result = _calculate(
10
+ close=close,
11
+ volume=volume,
12
+ length=length,
13
+ coef=coef,
14
+ vcoef=vcoef,
15
+ mamode=mamode,
16
+ talib=talib,
17
+ )
18
+ return named_series(result, "ta.vfi", "vfi")
@@ -0,0 +1,10 @@
1
+ """ta.vosc: Computation wrapper for the pinned dependency with canonical outputs."""
2
+
3
+ from pandas_ta_classic.volume.vosc import vosc as _calculate
4
+
5
+ from ..common import named_series
6
+
7
+
8
+ def compute(*, volume, fast, slow, talib):
9
+ result = _calculate(volume=volume, fast=fast, slow=slow, talib=talib)
10
+ return named_series(result, "ta.vosc", "vosc")
@@ -0,0 +1,10 @@
1
+ """ta.wad: Computation wrapper for the pinned dependency with canonical outputs."""
2
+
3
+ from pandas_ta_classic.volume.wad import wad as _calculate
4
+
5
+ from ..common import named_series
6
+
7
+
8
+ def compute(*, high, low, close, talib):
9
+ result = _calculate(high=high, low=low, close=close, talib=talib)
10
+ return named_series(result, "ta.wad", "wad")
@@ -0,0 +1,9 @@
1
+ """ta.wvad: Independent formula with a fixed initialization contract."""
2
+
3
+ from ..calculus import clean, ratio
4
+
5
+
6
+ def compute(*, open_, high, low, close, volume):
7
+ return ratio(clean(volume) * (clean(close) - clean(open_)), clean(high) - clean(low)).rename(
8
+ "wvad"
9
+ )
@@ -0,0 +1,51 @@
1
+ """Compatibility exports for the old inputs path; implemented in strategy.inputs."""
2
+
3
+ from openbacktesting.strategy.inputs import (
4
+ _SAFE_INT, # noqa: F401 — re-export the legacy entry point
5
+ _UNSET, # noqa: F401 — re-export the legacy entry point
6
+ SCHEMA_VERSION,
7
+ InputError,
8
+ InputSpec,
9
+ ResolvedInputs,
10
+ _collect, # noqa: F401 — re-export the legacy entry point
11
+ _definitions, # noqa: F401 — re-export the legacy entry point
12
+ _fail, # noqa: F401 — re-export the legacy entry point
13
+ _make, # noqa: F401 — re-export the legacy entry point
14
+ _resolve, # noqa: F401 — re-export the legacy entry point
15
+ _scalar, # noqa: F401 — re-export the legacy entry point
16
+ _schema, # noqa: F401 — re-export the legacy entry point
17
+ _session, # noqa: F401 — re-export the legacy entry point
18
+ bool,
19
+ color,
20
+ describe,
21
+ enum,
22
+ float,
23
+ int,
24
+ price,
25
+ request_for,
26
+ resolve_native,
27
+ resolve_request,
28
+ session,
29
+ string,
30
+ time,
31
+ )
32
+
33
+ __all__ = [
34
+ "SCHEMA_VERSION",
35
+ "InputError",
36
+ "InputSpec",
37
+ "ResolvedInputs",
38
+ "bool",
39
+ "color",
40
+ "describe",
41
+ "enum",
42
+ "float",
43
+ "int",
44
+ "price",
45
+ "request_for",
46
+ "resolve_native",
47
+ "resolve_request",
48
+ "session",
49
+ "string",
50
+ "time",
51
+ ]
openbacktesting/lib.py ADDED
@@ -0,0 +1,31 @@
1
+ """Legacy entry point for convenience APIs; each domain directly re-exports the same objects."""
2
+
3
+ from openbacktesting.data.aggregation import OHLCV_AGG, TRADES_AGG
4
+ from openbacktesting.data.generators import random_ohlc_data
5
+ from openbacktesting.portfolio.helpers import compute_stats
6
+ from openbacktesting.simulation.core import Backtest # noqa: F401
7
+ from openbacktesting.simulation.variants import FractionalBacktest, MultiBacktest
8
+ from openbacktesting.strategy.base import Strategy # noqa: F401
9
+ from openbacktesting.strategy.helpers import barssince, quantile, resample_apply
10
+ from openbacktesting.strategy.templates import SignalStrategy, TrailingStrategy
11
+
12
+ __pdoc__ = {
13
+ "Strategy.__init__": False,
14
+ "SignalStrategy.__init__": False,
15
+ "TrailingStrategy.__init__": False,
16
+ }
17
+
18
+ __all__ = [ # noqa: RUF022 — preserve existing export order
19
+ "OHLCV_AGG",
20
+ "TRADES_AGG",
21
+ "barssince",
22
+ "quantile",
23
+ "compute_stats",
24
+ "resample_apply",
25
+ "random_ohlc_data",
26
+ "SignalStrategy",
27
+ "TrailingStrategy",
28
+ "FractionalBacktest",
29
+ "FractionalBacktest", # noqa: RUF068 — preserve existing duplicate exports
30
+ "MultiBacktest",
31
+ ]
@@ -0,0 +1,129 @@
1
+ """Independent strategy logs for each historical run; no connection to global Python logging or output streams."""
2
+
3
+ from contextlib import contextmanager as _contextmanager
4
+ from datetime import UTC
5
+ from datetime import datetime as _datetime
6
+ from inspect import currentframe as _currentframe
7
+ from threading import Lock as _Lock
8
+ from typing import NamedTuple as _NamedTuple
9
+
10
+
11
+ class _Record(_NamedTuple):
12
+ sequence: int
13
+ level: str
14
+ message: str
15
+ phase: str
16
+ bar_index: int | None
17
+ bar_time: str | None
18
+ time_kind: str
19
+ recorded_at: str
20
+ source_file: str | None
21
+ source_line: int | None
22
+
23
+
24
+ class _Collector:
25
+ def __init__(self, *, enabled=True):
26
+ self.enabled = enabled
27
+ self._records: list[_Record] = []
28
+ self._context = None
29
+ self._closed = False
30
+
31
+ @_contextmanager
32
+ def phase(self, phase, bar_index=None, bar_time=None, time_kind="none"):
33
+ if self._closed:
34
+ raise RuntimeError("Strategy log session has ended")
35
+ self._context = (phase, bar_index, bar_time, time_kind)
36
+ try:
37
+ yield
38
+ finally:
39
+ self._context = None
40
+
41
+ def close(self):
42
+ self._closed = True
43
+ self._context = None
44
+
45
+ def snapshot(self) -> list[dict[str, str | int | None]]:
46
+ return [record._asdict() for record in self._records]
47
+
48
+
49
+ class StrategyLog:
50
+ """Read-only strategy log handle, accessed through self.log without author construction.
51
+
52
+ Record strings inside active author callbacks; error denotes only a level and does not stop the backtest.
53
+ Automatically record the actual calling file and line; calls after termination raise RuntimeError.
54
+ """
55
+
56
+ def __init__(self, collector: _Collector):
57
+ self._collector = collector
58
+
59
+ def info(self, message: str) -> None:
60
+ """Record informational messages, preserving empty strings, Chinese text, and newlines."""
61
+ self._write("info", message)
62
+
63
+ def warning(self, message: str) -> None:
64
+ """Record warning messages without changing strategy execution."""
65
+ self._write("warning", message)
66
+
67
+ def error(self, message: str) -> None:
68
+ """Record error-level messages; authors must explicitly raise to stop the strategy."""
69
+ self._write("error", message)
70
+
71
+ def _write(self, level, message):
72
+ if not isinstance(message, str):
73
+ raise TypeError("Strategy log message must be a string")
74
+ collector = self._collector
75
+ if collector._context is None:
76
+ raise RuntimeError("Strategy logs can only be written inside active author callbacks")
77
+ if not collector.enabled:
78
+ return
79
+ source_file, source_line = None, None
80
+ frame = _currentframe()
81
+ caller = None
82
+ try:
83
+ # Skip this function and the three public level methods explicitly; do not scan author helper functions.
84
+ caller = frame.f_back if frame is not None else None
85
+ caller = caller.f_back if caller is not None else None
86
+ if caller is not None:
87
+ filename, lineno = caller.f_code.co_filename, caller.f_lineno
88
+ if filename and lineno > 0:
89
+ source_file, source_line = filename, lineno
90
+ finally:
91
+ del frame, caller
92
+ collector._records.append(
93
+ _Record(
94
+ len(collector._records) + 1,
95
+ level,
96
+ message,
97
+ *collector._context,
98
+ _datetime.now(UTC).isoformat(timespec="microseconds"),
99
+ source_file,
100
+ source_line,
101
+ )
102
+ )
103
+
104
+
105
+ class RunLogs:
106
+ """Host-owned single-run log session, injected through runtime.run(..., logs=session).
107
+
108
+ Sequential reuse clears records before strategy discovery; a session cannot serve concurrent runs.
109
+ Queryable after success or exceptional exit; returns list and dictionary copies without holding author stack frames.
110
+ """
111
+
112
+ def __init__(self):
113
+ self._collector = _Collector()
114
+ self._lock = _Lock()
115
+
116
+ def get_logs(self) -> list[dict[str, str | int | None]]:
117
+ """Return an independent copy of all logs from this run; an empty list before the first run."""
118
+ return self._collector.snapshot()
119
+
120
+ @_contextmanager
121
+ def _capture(self):
122
+ if not self._lock.acquire(blocking=False):
123
+ raise RuntimeError("RunLogs is already occupied by another run")
124
+ try:
125
+ self._collector = _Collector()
126
+ yield
127
+ finally:
128
+ self._collector.close()
129
+ self._lock.release()
@@ -0,0 +1,8 @@
1
+ """Compatibility exports for the old optimization path; implemented in backtest.optimization."""
2
+
3
+ from openbacktesting.simulation.optimization import (
4
+ _joined_pool, # noqa: F401 — re-export the legacy entry point
5
+ _optimize, # noqa: F401 — re-export the legacy entry point
6
+ )
7
+
8
+ __all__ = []
@@ -0,0 +1,25 @@
1
+ """Exhaustive parameter search, local storage of individual Portfolios, and subsequent queries."""
2
+
3
+ from openbacktesting.optimizer import identity as identity
4
+ from openbacktesting.optimizer.models import (
5
+ ExecutionIdentity,
6
+ OptimizationError,
7
+ Plan,
8
+ Range,
9
+ Values,
10
+ )
11
+ from openbacktesting.optimizer.plan import plan
12
+ from openbacktesting.optimizer.query import ExperimentResult, open_result
13
+ from openbacktesting.optimizer.runner import execute
14
+
15
+ __all__ = [
16
+ "ExecutionIdentity",
17
+ "ExperimentResult",
18
+ "OptimizationError",
19
+ "Plan",
20
+ "Range",
21
+ "Values",
22
+ "execute",
23
+ "open_result",
24
+ "plan",
25
+ ]
@@ -0,0 +1,155 @@
1
+ # Generated by scripts/generate-contracts.mjs; do not edit manually.
2
+ """Cross-table Portfolio semantics; Schema ensures structure first, then this layer rejects inconsistent references."""
3
+
4
+ import math
5
+
6
+
7
+ def check_portfolio(p):
8
+ def require(ok, reason):
9
+ if not ok:
10
+ raise ValueError("portfolio-result: " + reason)
11
+
12
+ def unique(rows, key):
13
+ values = [r[key] for r in rows]
14
+ require(len(values) == len(set(values)), "duplicate_" + key)
15
+ return {r[key]: r for r in rows}
16
+
17
+ def close(a, b):
18
+ return math.isclose(a, b, rel_tol=1e-9, abs_tol=1e-8)
19
+
20
+ facts, trades, series = p["facts"], p["trades"], p["series"]
21
+ orders = unique(facts["orders"], "order_id")
22
+ fills = unique(facts["fills"], "fill_id")
23
+ allocations = unique(facts["allocations"], "allocation_id")
24
+ observations = unique(series["account"], "observationId")
25
+ fragments = unique(trades["closedFragments"], "fragmentId")
26
+ unique(trades["openLots"], "lotId")
27
+ require(bool(observations), "empty_observations")
28
+ require(
29
+ [r["observationSequence"] for r in observations.values()]
30
+ == list(range(1, len(observations) + 1)),
31
+ "observation_sequence",
32
+ )
33
+ require(
34
+ all(
35
+ a["barIndex"] <= b["barIndex"] for a, b in zip(series["account"], series["account"][1:])
36
+ ),
37
+ "bar_order",
38
+ )
39
+ require(series["account"][-1]["barIndex"] == p["run"]["processedThroughBar"], "executed_range")
40
+ for r in series["account"]:
41
+ require(0 <= r["barIndex"] <= p["run"]["processedThroughBar"], "bar_range")
42
+ for rows in (facts["orderSizing"], facts["orderEvents"], facts["fills"]):
43
+ require(all(r["order_id"] in orders for r in rows), "order_reference")
44
+ for table in (facts["fills"], facts["orderEvents"]):
45
+ seq = [r["sequence"] for r in table]
46
+ require(seq == sorted(set(seq)), "event_sequence")
47
+ require(
48
+ all(
49
+ r["fillSequence"] in {0} | {f["sequence"] for f in fills.values()}
50
+ for r in series["account"]
51
+ ),
52
+ "fill_sequence_reference",
53
+ )
54
+ for a in allocations.values():
55
+ require(a["fill_id"] in fills and a["entry_fill_id"] in fills, "fill_reference")
56
+ require(
57
+ set(fragments)
58
+ == {a["allocation_id"] for a in allocations.values() if a["kind"] == "close"},
59
+ "fragment_allocations",
60
+ )
61
+ require(
62
+ [t["displayOrdinal"] for t in fragments.values()] == list(range(1, len(fragments) + 1)),
63
+ "fragment_order",
64
+ )
65
+ for t in list(fragments.values()) + trades["openLots"]:
66
+ require(
67
+ t["entry"]["fillId"] in fills and t["entry"]["orderId"] in orders, "entry_reference"
68
+ )
69
+ for t in fragments.values():
70
+ require(t["exit"]["fillId"] in fills and t["exit"]["orderId"] in orders, "exit_reference")
71
+ require(allocations[t["fragmentId"]]["trade_id"] == t["lotId"], "lot_reference")
72
+ require(close(t["pnlNet"], t["pnlGross"] - t["feeTotal"]), "trade_accounting")
73
+ require(all(t["asOf"] in observations for t in trades["openLots"]), "open_asof")
74
+ for row in series["close"]:
75
+ require(row == observations.get(row["observationId"]), "close_reference")
76
+ require(len(series["margin"]) == len(observations), "margin_length")
77
+ for row, account in zip(series["margin"], series["account"]):
78
+ require(row["observationId"] == account["observationId"], "margin_axis")
79
+ for curve in series["directions"].values():
80
+ require(len(curve) == len(observations), "direction_length")
81
+ for row, account in zip(curve, series["account"]):
82
+ require(row["observationId"] == account["observationId"], "direction_axis")
83
+ initial = p["summary"]["all"]["initialCapital"]["value"]
84
+ for row, long, short in zip(
85
+ series["account"], series["directions"]["long"], series["directions"]["short"]
86
+ ):
87
+ require(
88
+ close(row["equity"] - initial, long["contribution"] + short["contribution"]),
89
+ "direction_accounting",
90
+ )
91
+ require(
92
+ close(sum(f["fee"] for f in fills.values()), sum(a["fee"] for a in allocations.values())),
93
+ "fee_conservation",
94
+ )
95
+ require(p["summary"]["all"]["closedFragmentCount"]["value"] == len(fragments), "closed_count")
96
+ require(p["summary"]["all"]["openLotCount"]["value"] == len(trades["openLots"]), "open_count")
97
+ for row in series["closedCumulative"]:
98
+ require(row["fragmentId"] in fragments, "cumulative_reference")
99
+ for bucket in p["analysis"]["distribution"]["histogram"]:
100
+ require(bucket["count"] == len(bucket["fragmentIds"]), "histogram_count")
101
+ require(all(ref in fragments for ref in bucket["fragmentIds"]), "histogram_reference")
102
+
103
+ histogram_ids = [
104
+ ref
105
+ for bucket in p["analysis"]["distribution"]["histogram"]
106
+ for ref in bucket["fragmentIds"]
107
+ ]
108
+ require(
109
+ len(histogram_ids) == len(fragments) and set(histogram_ids) == set(fragments),
110
+ "histogram_coverage",
111
+ )
112
+ for t in list(fragments.values()) + trades["openLots"]:
113
+ for leg in (t["entry"], t["exit"]):
114
+ if leg is not None:
115
+ fill = fills[leg["fillId"]]
116
+ require(
117
+ leg["orderId"] == fill["order_id"]
118
+ and leg["sequence"] == fill["sequence"]
119
+ and leg["barIndex"] == fill["bar_index"]
120
+ and leg["price"] == fill["price"],
121
+ "leg_fill_consistency",
122
+ )
123
+ total = sum(t["pnlNet"] for t in fragments.values()) + sum(
124
+ t["openPnlNet"] for t in trades["openLots"]
125
+ )
126
+ require(close(total, series["account"][-1]["equity"] - initial), "portfolio_accounting")
127
+ for name in ("drawdowns", "alternating"):
128
+ for episode in p["analysis"]["growthAndDecline"][name]:
129
+ require(
130
+ all(episode[k] in observations for k in ("start", "end", "extreme")),
131
+ "episode_reference",
132
+ )
133
+ for ref in p["analysis"]["distribution"]["outliers"]["fragmentIds"]:
134
+ require(ref in fragments, "outlier_reference")
135
+ for streak in p["analysis"]["streaks"]["rows"]:
136
+ require(
137
+ streak["startFragmentId"] in fragments and streak["endFragmentId"] in fragments,
138
+ "streak_reference",
139
+ )
140
+
141
+ def metrics(value):
142
+ if isinstance(value, dict):
143
+ if "methodId" in value and "unit" in value and "status" in value:
144
+ missing = value["value"] is None
145
+ require((value["status"] in ("ok", "approximate")) != missing, "metric_status")
146
+ require(not missing or bool(value["reason"]), "metric_reason")
147
+ for item in value.values():
148
+ metrics(item)
149
+ elif isinstance(value, list):
150
+ for item in value:
151
+ metrics(item)
152
+
153
+ metrics(p["summary"])
154
+ metrics(p["analysis"])
155
+ metrics(p["extensions"])
@@ -0,0 +1,45 @@
1
+ """Calculate experiment identity only for stably encodable market data and host declarations."""
2
+
3
+ import hashlib
4
+ import platform
5
+ from importlib.metadata import version
6
+
7
+ import pandas as pd
8
+
9
+ from openbacktesting.optimizer.models import ExecutionIdentity, digest
10
+
11
+
12
+ def execution_identity(data, identity, backtest):
13
+ if not isinstance(identity, ExecutionIdentity):
14
+ raise TypeError("optimizer: identity must be an ExecutionIdentity")
15
+ if not isinstance(data, pd.DataFrame) or not isinstance(data.index, pd.DatetimeIndex):
16
+ raise TypeError("optimizer: data must be a DataFrame with a datetime index")
17
+ if not data.columns.is_unique or any(type(c) is not str for c in data.columns):
18
+ raise ValueError("optimizer: market-data column names must be unique strings")
19
+ for dtype in data.dtypes:
20
+ if dtype.kind not in "biufMm" or str(dtype) == "object":
21
+ raise TypeError("optimizer: market data may contain only numeric, boolean, and datetime columns")
22
+ hashes = pd.util.hash_pandas_object(data, index=True).to_numpy().astype("<u8").tobytes()
23
+ data_hash = hashlib.sha256(hashes).hexdigest()
24
+ return {
25
+ "identityVersion": 1,
26
+ "source": identity.strategy_sha256,
27
+ "dependencies": identity.dependencies_sha256,
28
+ "data": digest(
29
+ {
30
+ "hash": data_hash,
31
+ "columns": list(data.columns),
32
+ "dtypes": list(map(str, data.dtypes)),
33
+ "index": str(data.index.dtype),
34
+ "indexName": data.index.name,
35
+ }
36
+ ),
37
+ "environment": {
38
+ "python": platform.python_version(),
39
+ **{
40
+ name: version(name)
41
+ for name in ("openbacktesting", "numpy", "pandas", "pandas-ta-classic")
42
+ },
43
+ },
44
+ "backtest": backtest,
45
+ }
@@ -0,0 +1,92 @@
1
+ """Plain-value types and canonical encoding for parameter experiments."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import re
8
+ from dataclasses import dataclass
9
+
10
+
11
+ def encode(value) -> str:
12
+ """Canonical JSON; reject nonfinite numbers and nonstring keys."""
13
+
14
+ def check(item):
15
+ if isinstance(item, dict):
16
+ if any(type(k) is not str for k in item):
17
+ raise ValueError("optimizer: JSON keys must be strings")
18
+ for child in item.values():
19
+ check(child)
20
+ elif isinstance(item, (list, tuple)):
21
+ for child in item:
22
+ check(child)
23
+
24
+ check(value)
25
+ return json.dumps(
26
+ value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
27
+ )
28
+
29
+
30
+ def digest(value) -> str:
31
+ return hashlib.sha256(encode(value).encode()).hexdigest()
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class Values:
36
+ """Finite candidate values; plan validates against the corresponding input declaration."""
37
+
38
+ values: tuple | list
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class Range:
43
+ """Ascending grid range; int uses integers, float/price uses decimal strings or integers."""
44
+
45
+ start: int | str
46
+ stop: int | str
47
+ step: int | str
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class ExecutionIdentity:
52
+ """Host declaration of SHA256 digests for loaded source and local dependencies; no files are loaded or executed."""
53
+
54
+ strategy_sha256: str
55
+ dependencies_sha256: str
56
+
57
+ def __post_init__(self):
58
+ for value in (self.strategy_sha256, self.dependencies_sha256):
59
+ if not isinstance(value, str) or re.fullmatch("[0-9a-f]{64}", value) is None:
60
+ raise ValueError("optimizer: execution identity must be a lowercase SHA256 digest")
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class Plan:
65
+ """Immutable canonical text; snapshot returns an independent copy, and combinations are generated incrementally at runtime."""
66
+
67
+ _json: str
68
+
69
+ def snapshot(self) -> dict:
70
+ """Return an independent JSON copy of the complete plan."""
71
+ return json.loads(self._json)
72
+
73
+ @property
74
+ def total(self) -> int:
75
+ """Raw Cartesian-product count, without deducting constraints not yet checked."""
76
+ return self.snapshot()["total"]
77
+
78
+
79
+ class OptimizationError(RuntimeError):
80
+ """Experiment infrastructure error preserving result location, identity, and last known committed count."""
81
+
82
+ def __init__(self, message, path, experiment_id, committed):
83
+ self.path = str(path)
84
+ self.experiment_id = experiment_id
85
+ self.committed = committed
86
+ super().__init__(
87
+ f"{message}; path={path}; experiment={experiment_id}; committed={committed}"
88
+ )
89
+
90
+
91
+ class _ExportError(ValueError):
92
+ """A backtest returned, but complete Portfolio export failed."""