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,129 @@
1
+ """Finite parameter-domain validation and stable Cartesian products without executing user strategies."""
2
+
3
+ from collections.abc import Mapping
4
+ from decimal import Decimal, InvalidOperation
5
+ from fractions import Fraction
6
+ from itertools import product
7
+ from math import prod
8
+
9
+ from openbacktesting.optimizer.models import Plan, Range, Values, encode
10
+ from openbacktesting.strategy.inputs import _definitions, describe, resolve_request
11
+
12
+ MAX_DOMAIN = 100_000
13
+ MAX_TOTAL = 2**63 - 1
14
+
15
+
16
+ def _decimal(value):
17
+ if type(value) not in (str, int) or len(str(value)) > 128:
18
+ raise ValueError("optimizer: ranges require integers or finite decimal strings")
19
+ try:
20
+ number = Decimal(value)
21
+ except InvalidOperation as error:
22
+ raise ValueError("optimizer: invalid decimal range") from error
23
+ if not number.is_finite() or not -324 <= number.as_tuple().exponent <= 308:
24
+ raise ValueError("optimizer: decimal range exceeds supported precision")
25
+ return Fraction(number)
26
+
27
+
28
+ def _domain(spec, domain):
29
+ if isinstance(domain, Values):
30
+ values = domain.values
31
+ if not isinstance(values, (tuple, list)) or not 0 < len(values) <= MAX_DOMAIN:
32
+ raise ValueError(f"optimizer: {spec.title} domain requires 1..{MAX_DOMAIN} candidates")
33
+ expression = {"values": []}
34
+ elif isinstance(domain, Range):
35
+ if spec.kind not in ("int", "float", "price"):
36
+ raise ValueError(f"optimizer: {spec.title} does not support numeric ranges")
37
+ raw = (domain.start, domain.stop, domain.step)
38
+ if spec.kind == "int" and any(type(v) is not int for v in raw):
39
+ raise ValueError("optimizer: int ranges accept only integers")
40
+ start, stop, step = map(_decimal, raw)
41
+ if step <= 0 or start > stop:
42
+ raise ValueError("optimizer: range must be ascending with a positive step")
43
+ count = (stop - start) // step + 1
44
+ if count > MAX_DOMAIN:
45
+ raise ValueError("optimizer: single-dimension candidates exceed the resource limit")
46
+ convert = int if spec.kind == "int" else float
47
+ values = [convert(start + i * step) for i in range(count)]
48
+ expression = {"range": {"start": domain.start, "stop": domain.stop, "step": domain.step}}
49
+ else:
50
+ raise TypeError("optimizer: search domain must be Values or Range")
51
+ encoded = []
52
+ seen = set()
53
+ for value in values:
54
+ normalized = spec.normalize(value, wire=spec.kind == "enum" and type(value) is str)
55
+ value = spec.encode(normalized)
56
+ key = encode(0.0 if type(value) is float and value == 0 else value)
57
+ if key in seen:
58
+ raise ValueError(f"optimizer: {spec.title} Duplicate normalized candidates")
59
+ seen.add(key)
60
+ encoded.append(value)
61
+ if "values" in expression:
62
+ expression["values"] = encoded
63
+ return {"title": spec.title, "values": encoded, "expression": expression}
64
+
65
+
66
+ def plan(
67
+ strategy_class,
68
+ search: Mapping[str, Values | Range],
69
+ *,
70
+ base_inputs=None,
71
+ execution: Mapping,
72
+ constraint_id: str | None = None,
73
+ ) -> Plan:
74
+ """Freeze complete current configuration and selected dimensions; the host must explicitly supply execution identity.
75
+
76
+ Args:
77
+ strategy_class: Loaded strategy class declaring inputs.
78
+ search: Nonempty search domains keyed by unique titles.
79
+ base_inputs: Existing title-based request; None uses declared defaults.
80
+ execution: Normalized code, market-data, configuration, and version identity.
81
+ constraint_id: Optional version digest of a trusted constraint; the function is not serialized.
82
+ """
83
+ description = describe(strategy_class)
84
+ request = (
85
+ base_inputs
86
+ if base_inputs is not None
87
+ else {
88
+ "schemaVersion": description["schemaVersion"],
89
+ "schemaHash": description["schemaHash"],
90
+ "values": {},
91
+ }
92
+ )
93
+ resolved = resolve_request(strategy_class, request).snapshot()
94
+ resolved.pop("overriddenTitles")
95
+ fields = {spec.title: spec for spec in _definitions(strategy_class).values()}
96
+ if not isinstance(search, Mapping) or not search or set(search) - fields.keys():
97
+ raise ValueError("optimizer: search must be a nonempty mapping of declared titles")
98
+ if not isinstance(execution, Mapping) or not execution:
99
+ raise ValueError("optimizer: execution identity is missing")
100
+ if constraint_id is not None and (type(constraint_id) is not str or not constraint_id.strip()):
101
+ raise ValueError("optimizer: constraint_id must not be empty")
102
+ dimensions = [_domain(spec, search[title]) for title, spec in fields.items() if title in search]
103
+ total = prod(len(d["values"]) for d in dimensions)
104
+ if total > MAX_TOTAL:
105
+ raise ValueError("optimizer: combination count exceeds the storage range")
106
+ return Plan(
107
+ encode(
108
+ {
109
+ "version": 1,
110
+ "inputs": resolved,
111
+ "dimensions": dimensions,
112
+ "total": total,
113
+ "execution": dict(execution),
114
+ "constraintId": constraint_id,
115
+ "artifacts": "portfolio",
116
+ }
117
+ )
118
+ )
119
+
120
+
121
+ def combinations(plan_value):
122
+ source = plan_value.snapshot()
123
+ dimensions = source["dimensions"]
124
+ for ordinal, values in enumerate(product(*(d["values"] for d in dimensions))):
125
+ request = source["inputs"] | {
126
+ "values": source["inputs"]["values"]
127
+ | dict(zip((d["title"] for d in dimensions), values, strict=True))
128
+ }
129
+ yield ordinal, request