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,810 @@
1
+ """Native strategy interface and indicator registration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABCMeta, abstractmethod
6
+ from collections.abc import Callable
7
+ from difflib import get_close_matches
8
+ from itertools import chain
9
+ from typing import Literal, cast, overload
10
+
11
+ import numpy as np
12
+ import pandas as pd
13
+
14
+ from openbacktesting.broker.core import Order, OrderGroup, Position, Trade, _Broker
15
+ from openbacktesting.broker.instrument import InstrumentSpec
16
+ from openbacktesting.broker.models import (
17
+ AccountSnapshot,
18
+ ClosedTradeStats,
19
+ Fill,
20
+ OrderEvent,
21
+ RunSummary,
22
+ )
23
+ from openbacktesting.broker.requests import OrderRequest, _Expiry, _JsonValue
24
+ from openbacktesting.broker.sizing import UNSET, _SizeIntent, select_size
25
+ from openbacktesting.data.core import _Data
26
+ from openbacktesting.data.strategy_data import DATA_NAMES, StrategyData
27
+ from openbacktesting.data.timeframe import Timeframe
28
+ from openbacktesting.helpers import _as_str, _set_legacy_module, try_
29
+ from openbacktesting.indicators.methods import TechnicalAnalysis
30
+ from openbacktesting.indicators.runtime import compute
31
+ from openbacktesting.indicators.types import IndicatorCursor, IndicatorError, IndicatorHandle
32
+ from openbacktesting.logs import StrategyLog
33
+ from openbacktesting.plotting.collector import Plotting
34
+ from openbacktesting.strategy.bar_state import BarState
35
+ from openbacktesting.strategy.inputs import ResolvedInputs
36
+
37
+
38
+ class Strategy(metaclass=ABCMeta):
39
+ """Strategy base class: Register indicators in init and place orders from currently visible history in next.
40
+
41
+
42
+ A trading strategy base class. Extend this class and
43
+ override methods
44
+ `openbacktesting.engine.Strategy.init` and
45
+ `openbacktesting.engine.Strategy.next` to define
46
+ your own strategy.
47
+ """
48
+
49
+ _input_strategy_base = True
50
+ _reserved_data_names = DATA_NAMES | {
51
+ "plotting",
52
+ "timeframe",
53
+ "bar_index",
54
+ "bar_state",
55
+ "log",
56
+ "closed_trade_stats",
57
+ }
58
+
59
+ def __init__(self, broker, data, params):
60
+ for cls in type(self).__mro__:
61
+ if cls is not Strategy:
62
+ conflicts = (
63
+ DATA_NAMES
64
+ | {
65
+ "ta",
66
+ "plotting",
67
+ "timeframe",
68
+ "bar_index",
69
+ "bar_state",
70
+ "log",
71
+ "closed_trade_stats",
72
+ }
73
+ ).intersection(cls.__dict__)
74
+ if conflicts:
75
+ name = min(conflicts)
76
+ raise AttributeError(f"Strategy.{name} is a reserved attribute; rename the field or method with the same name")
77
+ self._bar_context: tuple[int, BarState] | None = None
78
+ self._indicators = []
79
+ self._indicator_cursor = IndicatorCursor(len(data))
80
+ self._broker: _Broker = broker
81
+ self._data: _Data = data
82
+ self._strategy_data = StrategyData(data, self._indicator_cursor)
83
+ self._ta = TechnicalAnalysis(self._register_indicator)
84
+ self._params = self._check_params(params)
85
+
86
+ def __setattr__(self, name, value):
87
+ if name in DATA_NAMES | {
88
+ "plotting",
89
+ "timeframe",
90
+ "bar_index",
91
+ "bar_state",
92
+ "log",
93
+ "closed_trade_stats",
94
+ }:
95
+ raise AttributeError(f"Strategy.{name} is a reserved attribute; rename the field or parameter with the same name")
96
+ super().__setattr__(name, value)
97
+
98
+ def __delattr__(self, name):
99
+ if name in DATA_NAMES | {
100
+ "plotting",
101
+ "timeframe",
102
+ "bar_index",
103
+ "bar_state",
104
+ "log",
105
+ "closed_trade_stats",
106
+ }:
107
+ raise AttributeError(f"Strategy.{name} is a reserved attribute and cannot be deleted; rename the field with the same name")
108
+ super().__delattr__(name)
109
+
110
+ @staticmethod
111
+ def _check_reserved_params(params):
112
+ conflicts = (
113
+ DATA_NAMES
114
+ | {"ta", "plotting", "timeframe", "bar_index", "bar_state", "log", "closed_trade_stats"}
115
+ ).intersection(params)
116
+ if conflicts:
117
+ name = min(conflicts)
118
+ raise AttributeError(f"Strategy.{name} is a reserved attribute; rename the parameter with the same name")
119
+
120
+ def _enter_bar(self, index: int, total: int):
121
+ self._bar_context = (index, BarState(index == 0, index == total - 1))
122
+
123
+ @property
124
+ def bar_index(self) -> int:
125
+ """Current zero-based data position; unavailable in init, on_finish, and after the run ends."""
126
+ if self._bar_context is None:
127
+ raise RuntimeError("bar_index: no current execution bar")
128
+ return self._bar_context[0]
129
+
130
+ @property
131
+ def bar_state(self) -> BarState:
132
+ """Frozen first/last snapshot for the current data position; early termination does not relabel the current position as the last bar."""
133
+ if self._bar_context is None:
134
+ raise RuntimeError("bar_state: no current execution bar")
135
+ return self._bar_context[1]
136
+
137
+ @property
138
+ def timeframe(self) -> Timeframe | None:
139
+ """This chart's timeframe; None without low-level context, with no inference from market data."""
140
+ context = getattr(self._data, "_context", None)
141
+ return context.timeframe if context is not None else None
142
+
143
+ @property
144
+ def log(self) -> StrategyLog:
145
+ """Structured logs for the current run; use info, warning, and error inside author callbacks."""
146
+ return self._log
147
+
148
+ @property
149
+ def plotting(self) -> Plotting:
150
+ """Pure plotting-data entry point for the current run; declare in init and write during runtime callbacks."""
151
+ return self._plotting
152
+
153
+ @property
154
+ def open(self) -> IndicatorHandle:
155
+ """Open-price series for the current run; [-1] is the current value."""
156
+ return self._strategy_data.get("open")
157
+
158
+ @property
159
+ def high(self) -> IndicatorHandle:
160
+ """High-price series for the current run."""
161
+ return self._strategy_data.get("high")
162
+
163
+ @property
164
+ def low(self) -> IndicatorHandle:
165
+ """Low-price series for the current run."""
166
+ return self._strategy_data.get("low")
167
+
168
+ @property
169
+ def close(self) -> IndicatorHandle:
170
+ """Close-price series for the current run, usable directly with self.ta in init."""
171
+ return self._strategy_data.get("close")
172
+
173
+ @property
174
+ def volume(self) -> IndicatorHandle:
175
+ """Volume series; missing Volume remains NaN."""
176
+ return self._strategy_data.get("volume")
177
+
178
+ @property
179
+ def hl2(self) -> IndicatorHandle:
180
+ """(High + Low) / 2, computed lazily per run."""
181
+ return self._strategy_data.get("hl2")
182
+
183
+ @property
184
+ def hlc3(self) -> IndicatorHandle:
185
+ """(High + Low + Close) / 3。"""
186
+ return self._strategy_data.get("hlc3")
187
+
188
+ @property
189
+ def hlcc4(self) -> IndicatorHandle:
190
+ """(High + Low + Close + Close) / 4, excluding Open."""
191
+ return self._strategy_data.get("hlcc4")
192
+
193
+ @property
194
+ def data_index(self) -> pd.Index:
195
+ """Currently visible index; saved pandas objects do not update with the cursor."""
196
+ return self.data.index
197
+
198
+ @property
199
+ def df(self) -> pd.DataFrame:
200
+ """Currently visible original market-data table; no guarantee of deep copying or content isolation."""
201
+ return self.data.df
202
+
203
+ @property
204
+ def pip(self) -> float:
205
+ """Retains full-sample decimal-precision estimation; does not represent an exchange minimum step."""
206
+ return self.data.pip
207
+
208
+ @property
209
+ def ta(self) -> TechnicalAnalysis:
210
+ """Indicator entry point for the current run, such as ta.ema(length=20) or ta.macd().histogram."""
211
+ return self._ta
212
+
213
+ @ta.setter
214
+ def ta(self, value):
215
+ raise AttributeError("Strategy.ta is a reserved attribute; rename the field or parameter with the same name")
216
+
217
+ def __repr__(self):
218
+ return "<Strategy " + str(self) + ">"
219
+
220
+ def __str__(self):
221
+ params = ",".join(
222
+ f"{i[0]}={i[1]}" for i in zip(self._params.keys(), map(_as_str, self._params.values()))
223
+ )
224
+ if params:
225
+ params = "(" + params + ")"
226
+ return f"{self.__class__.__name__}{params}"
227
+
228
+ def _check_params(self, params):
229
+ if isinstance(params, ResolvedInputs):
230
+ self._resolved_inputs = params
231
+ return params.overrides()
232
+ self._check_reserved_params(params)
233
+ for k, v in params.items():
234
+ if not hasattr(self, k):
235
+ suggestions = get_close_matches(
236
+ k, (attr for attr in dir(self) if not attr.startswith("_"))
237
+ )
238
+ hint = f" Did you mean: {', '.join(suggestions)}?" if suggestions else ""
239
+ raise AttributeError(
240
+ f"Strategy '{self.__class__.__name__}' is missing parameter '{k}'. "
241
+ "Strategy class should define parameters as class variables before they "
242
+ "can be optimized or run with." + hint
243
+ )
244
+ setattr(self, k, v)
245
+ return params
246
+
247
+ def ind(
248
+ self,
249
+ func: Callable,
250
+ *args,
251
+ name=None,
252
+ **kwargs,
253
+ ) -> IndicatorHandle:
254
+ """Compute custom indicators in bulk during initialization and return read-only handles read through the backtest cursor.
255
+
256
+ Register the same function with different parameters or reuse it across strategies. Each registration executes the
257
+ function without sharing a custom-computation cache; prefer ``self.ta`` for built-in formulas.
258
+ Functions receive complete history in init; authors must ensure computations depend only on current and past data.
259
+
260
+ Args:
261
+ func: Computation function returning one- or two-dimensional array-like output with time-axis length equal to market data.
262
+ Series/DataFrame indices must exactly match market data; arrays align by position.
263
+ *args: Positional arguments passed unchanged to the computation function.
264
+ name: Display name or sequence of multi-output channel names, with length equal to the channel count.
265
+ Defaults to the function and arguments; format fields in names are filled from computation arguments.
266
+ **kwargs: Keyword arguments passed unchanged to the computation function.
267
+
268
+ Returns:
269
+ Read-only IndicatorHandle. Access named channels with ``result['middle']``;
270
+ ``np.asarray()``, ``.s``, and ``.df`` return prefix snapshots at read time.
271
+
272
+ Raises:
273
+ IndicatorError: Registration outside init, changed market-data source, or misaligned pandas index.
274
+ TypeError: name is not a string or string sequence.
275
+ ValueError: Output shape, length, or channel-name count does not match.
276
+ RuntimeError: User-function computation failed, with the original exception retained as cause.
277
+
278
+ Note:
279
+ ready defaults to all True; valid depends on finite values. next runs on every bar starting with the first;
280
+ authors must check history length and valid for the channels they use. Invalid indicators do not suppress callbacks.
281
+ More complex output masks are not public parameters of this entry point.
282
+ """
283
+
284
+ if "warmup" in kwargs:
285
+ raise TypeError("warmup has been removed; next runs on every bar, so check indicator valid explicitly")
286
+ if self._indicator_cursor.phase != "init":
287
+ raise IndicatorError("invalid-phase", "custom", "Indicators may be registered only in init()")
288
+ self._strategy_data.validate()
289
+
290
+ def _format_name(name: str) -> str:
291
+ return name.format(
292
+ *map(_as_str, args), **dict(zip(kwargs.keys(), map(_as_str, kwargs.values())))
293
+ )
294
+
295
+ if name is None:
296
+ params = ",".join(filter(None, map(_as_str, chain(args, kwargs.values()))))
297
+ func_name = _as_str(func)
298
+ name = f"{func_name}({params})" if params else f"{func_name}"
299
+ elif isinstance(name, str):
300
+ name = _format_name(name)
301
+ elif try_(lambda: all(isinstance(item, str) for item in name), False):
302
+ name = [_format_name(item) for item in name]
303
+ else:
304
+ raise TypeError(
305
+ f"Unexpected `name=` type {type(name)}; expected `str` or `Sequence[str]`"
306
+ )
307
+
308
+ try:
309
+ value = func(*args, **kwargs)
310
+ except Exception as e:
311
+ raise RuntimeError(f'Indicator "{name}" error. See traceback above.') from e
312
+
313
+ if isinstance(value, (pd.Series, pd.DataFrame)) and not value.index.equals(self.data.index):
314
+ raise IndicatorError("index-mismatch", name, "Indicator index must match the market-data index")
315
+ if isinstance(value, pd.DataFrame):
316
+ value = value.values.T
317
+
318
+ if value is not None:
319
+ value = try_(lambda: np.asarray(value, order="C"), None)
320
+ is_arraylike = bool(value is not None and value.shape)
321
+
322
+ # Optionally flip the array if the user returned e.g. `df.values`
323
+ if is_arraylike and value.shape[0] == len(self._data):
324
+ value = value.T
325
+
326
+ if isinstance(name, list) and (np.atleast_2d(value).shape[0] != len(name)):
327
+ raise ValueError(
328
+ f"Length of `name=` ({len(name)}) must agree with the number "
329
+ f"of arrays the indicator returns ({value.shape[0]})."
330
+ )
331
+
332
+ if not is_arraylike or not 1 <= value.ndim <= 2 or value.shape[-1] != len(self._data):
333
+ raise ValueError(
334
+ "Indicators must return (optionally a tuple of) numpy.arrays of same "
335
+ f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}" '
336
+ f"shape: {getattr(value, 'shape', '')}, returned value: {value})"
337
+ )
338
+
339
+ value = IndicatorHandle(
340
+ value,
341
+ name=name,
342
+ index=self.data.index,
343
+ cursor=self._indicator_cursor,
344
+ metadata={"kind": "custom", "name": name, "cache": "disabled"},
345
+ )
346
+ self._indicators.append(value)
347
+ return value
348
+
349
+ def _register_indicator(
350
+ self, indicator, *, inputs=None, backend=None, required_outputs=None, **parameters
351
+ ):
352
+ """Register built-in indicators internally for self.ta; the owned catalog defines output names and parameters."""
353
+ if self._indicator_cursor.phase != "init":
354
+ raise IndicatorError("invalid-phase", indicator, "Indicators may be registered only in init()")
355
+ self._strategy_data.validate()
356
+ value = compute(
357
+ indicator,
358
+ self.data.df,
359
+ parameters=parameters,
360
+ inputs=inputs,
361
+ backend=backend,
362
+ required_outputs=required_outputs,
363
+ cache=self._data._indicator_cache,
364
+ cursor=self._indicator_cursor,
365
+ )
366
+ self._indicators.append(value)
367
+ return value
368
+
369
+ @abstractmethod
370
+ def init(self):
371
+ """Strategy initialization entry point for precomputation and indicator registration; complete historical data is accessible.
372
+
373
+
374
+ Initialize the strategy.
375
+ Override this method.
376
+ Declare indicators (with `openbacktesting.engine.Strategy.ind`).
377
+ Precompute what needs to be precomputed or can be precomputed
378
+ in a vectorized fashion before the strategy starts.
379
+
380
+ If you extend composable strategies from `openbacktesting.lib`,
381
+ make sure to call:
382
+
383
+ super().init()
384
+ """
385
+
386
+ @abstractmethod
387
+ def next(self):
388
+ """Per-bar strategy callback, starting at input bar 0 and called once for each normally reached bar.
389
+
390
+ Called even with insufficient or invalid indicator history; check length before [-2] and valid before consuming indicators.
391
+ bar_index/bar_state represent the current input position and frozen first/last state.
392
+
393
+
394
+ Main strategy runtime method, called as each new
395
+ `openbacktesting.engine.Strategy.data`
396
+ instance (row; full candlestick bar) becomes available.
397
+ This is the main method where strategy decisions
398
+ upon data precomputed in `openbacktesting.engine.Strategy.init`
399
+ take place.
400
+
401
+ If you extend composable strategies from `openbacktesting.lib`,
402
+ make sure to call:
403
+
404
+ super().next()
405
+ """
406
+
407
+ def on_start(self):
408
+ """One notification after initialization completes; order submission is prohibited here."""
409
+
410
+ def on_finish(self, summary: RunSummary):
411
+ """Receive a frozen finish summary, including exceptional termination; trading is prohibited."""
412
+
413
+ def on_order(self, event: OrderEvent):
414
+ """Receive frozen order events; new requests execute no earlier than the next bar."""
415
+
416
+ def on_fill(self, fill: Fill):
417
+ """Receive actual fill facts; callbacks do not recursively trigger matching."""
418
+
419
+ def _order_size(self, qty, amount, percent, reduce_only):
420
+ self._broker._permission()
421
+ settings = getattr(self._data, "_default_order_size", None)
422
+ default = None
423
+ if settings is not None:
424
+ default = _SizeIntent(
425
+ "qty" if settings.mode == "fixed" else "percent",
426
+ settings.value if settings.mode == "fixed" else settings.value * 100,
427
+ "default",
428
+ )
429
+ return select_size(
430
+ qty=qty, amount=amount, percent=percent, default=default, reduce_only=reduce_only
431
+ )
432
+
433
+ @overload
434
+ def buy(
435
+ self,
436
+ *,
437
+ qty: float,
438
+ limit: float | None = None,
439
+ stop: float | None = None,
440
+ sl: float | None = None,
441
+ tp: float | None = None,
442
+ tag: _JsonValue = None,
443
+ reduce_only: bool = False,
444
+ expires_at: _Expiry | None = None,
445
+ valid_bars: int | None = None,
446
+ ) -> Order:
447
+ """Explicit asset quantity; reduce-only is allowed."""
448
+
449
+ @overload
450
+ def buy(
451
+ self,
452
+ *,
453
+ amount: float,
454
+ limit: float | None = None,
455
+ stop: float | None = None,
456
+ sl: float | None = None,
457
+ tp: float | None = None,
458
+ tag: _JsonValue = None,
459
+ reduce_only: Literal[False] = False,
460
+ expires_at: _Expiry | None = None,
461
+ valid_bars: int | None = None,
462
+ ) -> Order:
463
+ """Explicit notional amount; reduce-only is not allowed."""
464
+
465
+ @overload
466
+ def buy(
467
+ self,
468
+ *,
469
+ percent: float,
470
+ limit: float | None = None,
471
+ stop: float | None = None,
472
+ sl: float | None = None,
473
+ tp: float | None = None,
474
+ tag: _JsonValue = None,
475
+ reduce_only: Literal[False] = False,
476
+ expires_at: _Expiry | None = None,
477
+ valid_bars: int | None = None,
478
+ ) -> Order:
479
+ """Explicit equity percentage; reduce-only is not allowed."""
480
+
481
+ @overload
482
+ def buy(
483
+ self,
484
+ *,
485
+ limit: float | None = None,
486
+ stop: float | None = None,
487
+ sl: float | None = None,
488
+ tp: float | None = None,
489
+ tag: _JsonValue = None,
490
+ reduce_only: Literal[False] = False,
491
+ expires_at: _Expiry | None = None,
492
+ valid_bars: int | None = None,
493
+ ) -> Order:
494
+ """Omit all sizing choices to use the configured default."""
495
+
496
+ def buy(
497
+ self,
498
+ *,
499
+ qty: float = cast(float, UNSET),
500
+ amount: float = cast(float, UNSET),
501
+ percent: float = cast(float, UNSET),
502
+ limit: float | None = None,
503
+ stop: float | None = None,
504
+ sl: float | None = None,
505
+ tp: float | None = None,
506
+ tag: _JsonValue = None,
507
+ reduce_only: bool = False,
508
+ expires_at: _Expiry | None = None,
509
+ valid_bars: int | None = None,
510
+ ) -> Order:
511
+ """Submit an incremental order; choose one of qty/amount/percent, where percent=20 means 20% of equity.
512
+
513
+ Omitting all choices uses the default quantity. Amount is a single-currency notional budget excluding fees;
514
+ convert and freeze it using Close at request time, with rejection still possible at execution. Reduce-only requires explicit qty.
515
+ """
516
+ return self._broker.submit_order(
517
+ self._order_size(qty, amount, percent, reduce_only),
518
+ side="buy",
519
+ limit=limit,
520
+ stop=stop,
521
+ sl=sl,
522
+ tp=tp,
523
+ tag=tag,
524
+ reduce_only=reduce_only,
525
+ expires_at=expires_at,
526
+ valid_bars=valid_bars,
527
+ )
528
+
529
+ @overload
530
+ def sell(
531
+ self,
532
+ *,
533
+ qty: float,
534
+ limit: float | None = None,
535
+ stop: float | None = None,
536
+ sl: float | None = None,
537
+ tp: float | None = None,
538
+ tag: _JsonValue = None,
539
+ reduce_only: bool = False,
540
+ expires_at: _Expiry | None = None,
541
+ valid_bars: int | None = None,
542
+ ) -> Order:
543
+ """Explicit asset quantity; reduce-only is allowed."""
544
+
545
+ @overload
546
+ def sell(
547
+ self,
548
+ *,
549
+ amount: float,
550
+ limit: float | None = None,
551
+ stop: float | None = None,
552
+ sl: float | None = None,
553
+ tp: float | None = None,
554
+ tag: _JsonValue = None,
555
+ reduce_only: Literal[False] = False,
556
+ expires_at: _Expiry | None = None,
557
+ valid_bars: int | None = None,
558
+ ) -> Order:
559
+ """Explicit notional amount; reduce-only is not allowed."""
560
+
561
+ @overload
562
+ def sell(
563
+ self,
564
+ *,
565
+ percent: float,
566
+ limit: float | None = None,
567
+ stop: float | None = None,
568
+ sl: float | None = None,
569
+ tp: float | None = None,
570
+ tag: _JsonValue = None,
571
+ reduce_only: Literal[False] = False,
572
+ expires_at: _Expiry | None = None,
573
+ valid_bars: int | None = None,
574
+ ) -> Order:
575
+ """Explicit equity percentage; reduce-only is not allowed."""
576
+
577
+ @overload
578
+ def sell(
579
+ self,
580
+ *,
581
+ limit: float | None = None,
582
+ stop: float | None = None,
583
+ sl: float | None = None,
584
+ tp: float | None = None,
585
+ tag: _JsonValue = None,
586
+ reduce_only: Literal[False] = False,
587
+ expires_at: _Expiry | None = None,
588
+ valid_bars: int | None = None,
589
+ ) -> Order:
590
+ """Omit all sizing choices to use the configured default."""
591
+
592
+ def sell(
593
+ self,
594
+ *,
595
+ qty: float = cast(float, UNSET),
596
+ amount: float = cast(float, UNSET),
597
+ percent: float = cast(float, UNSET),
598
+ limit: float | None = None,
599
+ stop: float | None = None,
600
+ sl: float | None = None,
601
+ tp: float | None = None,
602
+ tag: _JsonValue = None,
603
+ reduce_only: bool = False,
604
+ expires_at: _Expiry | None = None,
605
+ valid_bars: int | None = None,
606
+ ) -> Order:
607
+ """Submit an incremental order; choose one of qty/amount/percent, where percent=20 means 20% of equity.
608
+
609
+ Omitting all choices uses the default quantity. Amount is a single-currency notional budget excluding fees;
610
+ convert and freeze it using Close at request time, with rejection still possible at execution. Reduce-only requires explicit qty.
611
+ """
612
+ return self._broker.submit_order(
613
+ self._order_size(qty, amount, percent, reduce_only),
614
+ side="sell",
615
+ limit=limit,
616
+ stop=stop,
617
+ sl=sl,
618
+ tp=tp,
619
+ tag=tag,
620
+ reduce_only=reduce_only,
621
+ expires_at=expires_at,
622
+ valid_bars=valid_bars,
623
+ )
624
+
625
+ @property
626
+ def account(self) -> AccountSnapshot:
627
+ """Frozen snapshot of current balance, equity, actual fees, and margin."""
628
+ return self._broker.account
629
+
630
+ @property
631
+ def instrument(self) -> InstrumentSpec:
632
+ """Asset rules for this run."""
633
+ return self._broker.instrument
634
+
635
+ def close_position(self, portion: float | None = None) -> tuple[Order, ...]:
636
+ """Exit each lot; return an empty tuple when flat, while self.close remains market data."""
637
+ return self.position.close(portion)
638
+
639
+ def submit_oco(self, *, first: OrderRequest, second: OrderRequest) -> OrderGroup:
640
+ """Atomically submit two entry requests; validate both first, prioritize first, and return a queryable group."""
641
+ return self._broker.submit_oco(first=first, second=second)
642
+
643
+ def order_target_quantity(
644
+ self,
645
+ *,
646
+ target: float,
647
+ limit: float | None = None,
648
+ stop: float | None = None,
649
+ sl: float | None = None,
650
+ tp: float | None = None,
651
+ tag: _JsonValue = None,
652
+ reduce_only: bool = False,
653
+ expires_at: _Expiry | None = None,
654
+ valid_bars: int | None = None,
655
+ ) -> Order | None:
656
+ """Generate a difference order from the actual net position; always validate conditions and reuse identical active targets.
657
+
658
+ target is the target asset quantity; return None when no order is needed. Only netting mode is supported.
659
+ All arguments must be supplied by name; limit/stop/sl/tp are absolute prices.
660
+ Validate prices, expiration, and tags even with no difference; do not invent a buy/sell direction to check protection relationships.
661
+ """
662
+ return self._broker.order_target(
663
+ target,
664
+ {
665
+ "limit": limit,
666
+ "stop": stop,
667
+ "sl": sl,
668
+ "tp": tp,
669
+ "tag": tag,
670
+ "reduce_only": reduce_only,
671
+ "expires_at": expires_at,
672
+ "valid_bars": valid_bars,
673
+ },
674
+ )
675
+
676
+ def order_target_value(
677
+ self,
678
+ *,
679
+ target: float,
680
+ limit: float | None = None,
681
+ stop: float | None = None,
682
+ sl: float | None = None,
683
+ tp: float | None = None,
684
+ tag: _JsonValue = None,
685
+ reduce_only: bool = False,
686
+ expires_at: _Expiry | None = None,
687
+ valid_bars: int | None = None,
688
+ ) -> Order | None:
689
+ """Convert target net notional to quantity using Close at request time; explicit conditions match quantity targets."""
690
+ from openbacktesting.broker.models import finite
691
+
692
+ return self.order_target_quantity(
693
+ target=finite(target, "target") / self.close[-1],
694
+ limit=limit,
695
+ stop=stop,
696
+ sl=sl,
697
+ tp=tp,
698
+ tag=tag,
699
+ reduce_only=reduce_only,
700
+ expires_at=expires_at,
701
+ valid_bars=valid_bars,
702
+ )
703
+
704
+ def order_target_percent(
705
+ self,
706
+ *,
707
+ target: float,
708
+ limit: float | None = None,
709
+ stop: float | None = None,
710
+ sl: float | None = None,
711
+ tp: float | None = None,
712
+ tag: _JsonValue = None,
713
+ reduce_only: bool = False,
714
+ expires_at: _Expiry | None = None,
715
+ valid_bars: int | None = None,
716
+ ) -> Order | None:
717
+ """Determine target net notional from equity percentage at request time; no continuous rebalancing or guaranteed fill weight."""
718
+ from openbacktesting.broker.models import finite
719
+
720
+ if self.equity <= 0:
721
+ raise ValueError("Target percentage requires positive equity")
722
+ return self.order_target_value(
723
+ target=finite(target, "target") * self.equity,
724
+ limit=limit,
725
+ stop=stop,
726
+ sl=sl,
727
+ tp=tp,
728
+ tag=tag,
729
+ reduce_only=reduce_only,
730
+ expires_at=expires_at,
731
+ valid_bars=valid_bars,
732
+ )
733
+
734
+ @property
735
+ def equity(self) -> float:
736
+ """Current account equity, including cash and position value.
737
+
738
+ Current account equity (cash plus assets)."""
739
+ return self._broker.equity
740
+
741
+ @property
742
+ def data(self) -> _Data:
743
+ """Currently visible original market-data entry point; full history in init and only the current prefix in next.
744
+
745
+
746
+ Price data, roughly as passed into
747
+ `openbacktesting.engine.Backtest.__init__`,
748
+ but with two significant exceptions:
749
+
750
+ * `data` is _not_ a DataFrame, but a custom structure
751
+ that serves customized numpy arrays for reasons of performance
752
+ and convenience. Besides OHLCV columns, `.index` and length,
753
+ it offers `.pip` property, the smallest price unit of change.
754
+ * Within `openbacktesting.engine.Strategy.init`, `data` arrays
755
+ are available in full length, as passed into
756
+ `openbacktesting.engine.Backtest.__init__`
757
+ (for precomputing indicators and such). However, within
758
+ `openbacktesting.engine.Strategy.next`, `data` arrays are
759
+ only as long as the current iteration, simulating gradual
760
+ price point revelation. In each call of
761
+ `openbacktesting.engine.Strategy.next` (iteratively called by
762
+ `openbacktesting.engine.Backtest` internally),
763
+ the last array value (e.g. `data.Close[-1]`)
764
+ is always the **most recent** value.
765
+ * If you need data arrays (e.g. `data.Close`) to be indexed
766
+ **Pandas series**, you can call their `.s` accessor
767
+ (e.g. `data.Close.s`). If you need the whole of data
768
+ as a **DataFrame**, use `.df` accessor (i.e. `data.df`).
769
+ """
770
+ return self._data
771
+
772
+ @property
773
+ def position(self) -> Position:
774
+ """Current net-position aggregate, supporting size/profit reads and closure requests.
775
+
776
+ Instance of `openbacktesting.engine.Position`."""
777
+ return self._broker.position
778
+
779
+ @property
780
+ def orders(self) -> tuple[Order, ...]:
781
+ """Orders awaiting execution; cancel may be called on these orders.
782
+
783
+ List of orders (see `Order`) waiting for execution."""
784
+ return tuple(self._broker.orders)
785
+
786
+ @property
787
+ def trades(self) -> tuple[Trade, ...]:
788
+ """Currently held trades, distinct from settled closed_trades.
789
+
790
+ List of active trades (see `Trade`)."""
791
+ return tuple(self._broker.trades)
792
+
793
+ @property
794
+ def closed_trade_stats(self) -> ClosedTradeStats:
795
+ """Read frozen statistics for closed portions, including allocated fees and excluding unrealized profit or loss.
796
+
797
+ Reads do not scan history; available throughout the lifecycle and after the run ends.
798
+ same_close results are not visible before execution; saved values do not change with later fills.
799
+ """
800
+ return self._broker._closed_stats
801
+
802
+ @property
803
+ def closed_trades(self) -> tuple[Trade, ...]:
804
+ """Completed trades for individual-trade analysis.
805
+
806
+ List of settled trades (see `Trade`)."""
807
+ return tuple(self._broker.closed_trades)
808
+
809
+
810
+ _set_legacy_module(Strategy, "openbacktesting.engine")