opencode-pyneruntime 6.6.4__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 (261) hide show
  1. opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
  2. opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
  3. opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
  4. opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
  5. opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
  6. opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
  7. opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
  8. pynecore/__init__.py +6 -0
  9. pynecore/cli/__init__.py +2 -0
  10. pynecore/cli/app.py +238 -0
  11. pynecore/cli/commands/__init__.py +343 -0
  12. pynecore/cli/commands/benchmark.py +186 -0
  13. pynecore/cli/commands/compile.py +198 -0
  14. pynecore/cli/commands/data.py +857 -0
  15. pynecore/cli/commands/debug.py +63 -0
  16. pynecore/cli/commands/optimize.py +956 -0
  17. pynecore/cli/commands/plugin.py +242 -0
  18. pynecore/cli/commands/run.py +2006 -0
  19. pynecore/cli/pluggable.py +132 -0
  20. pynecore/cli/utils/__init__.py +0 -0
  21. pynecore/cli/utils/api_error_handler.py +168 -0
  22. pynecore/cli/utils/broker_picker.py +330 -0
  23. pynecore/cli/utils/error_hook.py +28 -0
  24. pynecore/cli/utils/keyreader.py +178 -0
  25. pynecore/cli/utils/provider_picker.py +19 -0
  26. pynecore/cli/utils/symbol_browser.py +1149 -0
  27. pynecore/core/__init__.py +0 -0
  28. pynecore/core/aggregator.py +257 -0
  29. pynecore/core/bar_magnifier.py +168 -0
  30. pynecore/core/broker/__init__.py +64 -0
  31. pynecore/core/broker/defaults.py +113 -0
  32. pynecore/core/broker/disappearance.py +927 -0
  33. pynecore/core/broker/emulator.py +345 -0
  34. pynecore/core/broker/exceptions.py +346 -0
  35. pynecore/core/broker/idempotency.py +401 -0
  36. pynecore/core/broker/intent_builder.py +334 -0
  37. pynecore/core/broker/journal.py +1785 -0
  38. pynecore/core/broker/models.py +1600 -0
  39. pynecore/core/broker/native_failsafe_manager.py +1436 -0
  40. pynecore/core/broker/one_way_emulator.py +1128 -0
  41. pynecore/core/broker/position.py +787 -0
  42. pynecore/core/broker/run_identity.py +126 -0
  43. pynecore/core/broker/software_entry_stop_engine.py +351 -0
  44. pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
  45. pynecore/core/broker/spot_inventory.py +1327 -0
  46. pynecore/core/broker/storage.py +2655 -0
  47. pynecore/core/broker/store_helpers.py +2161 -0
  48. pynecore/core/broker/sync_engine.py +16070 -0
  49. pynecore/core/broker/validation.py +382 -0
  50. pynecore/core/class_property.py +7 -0
  51. pynecore/core/config.py +392 -0
  52. pynecore/core/csv_file.py +547 -0
  53. pynecore/core/currency.py +262 -0
  54. pynecore/core/data_converter.py +1002 -0
  55. pynecore/core/datetime.py +296 -0
  56. pynecore/core/download_info.py +71 -0
  57. pynecore/core/download_runner.py +274 -0
  58. pynecore/core/htf_aggregator.py +181 -0
  59. pynecore/core/import_hook.py +358 -0
  60. pynecore/core/instance_state.py +494 -0
  61. pynecore/core/live_ltf_collector.py +442 -0
  62. pynecore/core/live_ltf_window.py +189 -0
  63. pynecore/core/live_runner.py +1347 -0
  64. pynecore/core/module_property.py +26 -0
  65. pynecore/core/ohlcv_file.py +1888 -0
  66. pynecore/core/overload.py +371 -0
  67. pynecore/core/pine_cast.py +113 -0
  68. pynecore/core/pine_export.py +95 -0
  69. pynecore/core/pine_method.py +244 -0
  70. pynecore/core/pine_range.py +86 -0
  71. pynecore/core/pine_udt.py +69 -0
  72. pynecore/core/plugin/__init__.py +394 -0
  73. pynecore/core/plugin/broker.py +781 -0
  74. pynecore/core/plugin/cli.py +96 -0
  75. pynecore/core/plugin/live_provider.py +208 -0
  76. pynecore/core/plugin/provider.py +331 -0
  77. pynecore/core/provider_string.py +148 -0
  78. pynecore/core/random.py +40 -0
  79. pynecore/core/resampler.py +686 -0
  80. pynecore/core/safe_convert.py +64 -0
  81. pynecore/core/script.py +1011 -0
  82. pynecore/core/script_runner.py +3202 -0
  83. pynecore/core/security.py +1749 -0
  84. pynecore/core/security_process.py +1253 -0
  85. pynecore/core/security_shm.py +456 -0
  86. pynecore/core/series.py +417 -0
  87. pynecore/core/strategy_stats.py +669 -0
  88. pynecore/core/symbol_map.py +134 -0
  89. pynecore/core/syminfo.py +505 -0
  90. pynecore/core/viz.py +591 -0
  91. pynecore/lib/__init__.py +1771 -0
  92. pynecore/lib/_fixnan.py +32 -0
  93. pynecore/lib/_math_stateful.py +202 -0
  94. pynecore/lib/_timeframe_change.py +101 -0
  95. pynecore/lib/adjustment.py +6 -0
  96. pynecore/lib/alert.py +39 -0
  97. pynecore/lib/alert.pyi +14 -0
  98. pynecore/lib/array.py +1051 -0
  99. pynecore/lib/barmerge.py +60 -0
  100. pynecore/lib/barstate.py +30 -0
  101. pynecore/lib/box.py +415 -0
  102. pynecore/lib/chart.py +128 -0
  103. pynecore/lib/color.py +152 -0
  104. pynecore/lib/color.pyi +50 -0
  105. pynecore/lib/currency.py +62 -0
  106. pynecore/lib/dayofweek.py +36 -0
  107. pynecore/lib/dayofweek.pyi +18 -0
  108. pynecore/lib/display.py +8 -0
  109. pynecore/lib/dividends.py +9 -0
  110. pynecore/lib/earnings.py +11 -0
  111. pynecore/lib/extend.py +6 -0
  112. pynecore/lib/font.py +5 -0
  113. pynecore/lib/footprint.py +79 -0
  114. pynecore/lib/format.py +11 -0
  115. pynecore/lib/hline.py +67 -0
  116. pynecore/lib/hline.pyi +24 -0
  117. pynecore/lib/label.py +409 -0
  118. pynecore/lib/line.py +433 -0
  119. pynecore/lib/linefill.py +93 -0
  120. pynecore/lib/location.py +11 -0
  121. pynecore/lib/log.py +362 -0
  122. pynecore/lib/map.py +150 -0
  123. pynecore/lib/math.py +385 -0
  124. pynecore/lib/matrix.py +708 -0
  125. pynecore/lib/order.py +8 -0
  126. pynecore/lib/pivotpointtype.py +8 -0
  127. pynecore/lib/plot.py +95 -0
  128. pynecore/lib/plot.pyi +33 -0
  129. pynecore/lib/polyline.py +91 -0
  130. pynecore/lib/position.py +15 -0
  131. pynecore/lib/request.py +281 -0
  132. pynecore/lib/runtime.py +5 -0
  133. pynecore/lib/scale.py +9 -0
  134. pynecore/lib/session.py +267 -0
  135. pynecore/lib/session.pyi +12 -0
  136. pynecore/lib/shape.py +18 -0
  137. pynecore/lib/size.py +12 -0
  138. pynecore/lib/splits.py +4 -0
  139. pynecore/lib/strategy/__init__.py +4778 -0
  140. pynecore/lib/strategy/closedtrades.py +347 -0
  141. pynecore/lib/strategy/closedtrades.pyi +53 -0
  142. pynecore/lib/strategy/commission.py +9 -0
  143. pynecore/lib/strategy/direction.py +9 -0
  144. pynecore/lib/strategy/oca.py +13 -0
  145. pynecore/lib/strategy/opentrades.py +281 -0
  146. pynecore/lib/strategy/opentrades.pyi +49 -0
  147. pynecore/lib/strategy/risk.py +109 -0
  148. pynecore/lib/string.py +649 -0
  149. pynecore/lib/syminfo.py +84 -0
  150. pynecore/lib/ta.py +2230 -0
  151. pynecore/lib/table.py +290 -0
  152. pynecore/lib/text.py +17 -0
  153. pynecore/lib/ticker.py +207 -0
  154. pynecore/lib/timeframe.py +293 -0
  155. pynecore/lib/volume_row.py +67 -0
  156. pynecore/lib/xloc.py +4 -0
  157. pynecore/lib/yloc.py +5 -0
  158. pynecore/providers/__init__.py +0 -0
  159. pynecore/providers/ccxt.py +664 -0
  160. pynecore/providers/replay.py +187 -0
  161. pynecore/pynesys/__init__.py +0 -0
  162. pynecore/pynesys/api.py +498 -0
  163. pynecore/pynesys/compiler.py +112 -0
  164. pynecore/standalone.py +99 -0
  165. pynecore/testing/__init__.py +1 -0
  166. pynecore/testing/broker_lab/__init__.py +41 -0
  167. pynecore/testing/broker_lab/__main__.py +5 -0
  168. pynecore/testing/broker_lab/cli.py +87 -0
  169. pynecore/testing/broker_lab/generate.py +47 -0
  170. pynecore/testing/broker_lab/model.py +84 -0
  171. pynecore/testing/broker_lab/reference.py +645 -0
  172. pynecore/testing/broker_lab/runner.py +372 -0
  173. pynecore/testing/broker_lab/scheduler.py +50 -0
  174. pynecore/testing/broker_lab/subprocess.py +73 -0
  175. pynecore/transformers/__init__.py +0 -0
  176. pynecore/transformers/builtin_shadow.py +136 -0
  177. pynecore/transformers/closure_arguments_transformer.py +428 -0
  178. pynecore/transformers/display_rewrite.py +140 -0
  179. pynecore/transformers/dynamic_default.py +147 -0
  180. pynecore/transformers/function_isolation.py +757 -0
  181. pynecore/transformers/import_lifter.py +61 -0
  182. pynecore/transformers/import_normalizer.py +328 -0
  183. pynecore/transformers/inline_series_hoist.py +178 -0
  184. pynecore/transformers/input_transformer.py +175 -0
  185. pynecore/transformers/lib_series.py +201 -0
  186. pynecore/transformers/locations.py +70 -0
  187. pynecore/transformers/module_properties.json +3387 -0
  188. pynecore/transformers/module_property.py +221 -0
  189. pynecore/transformers/ne_guard.py +70 -0
  190. pynecore/transformers/persistent.py +320 -0
  191. pynecore/transformers/persistent_series.py +76 -0
  192. pynecore/transformers/safe_convert_transformer.py +97 -0
  193. pynecore/transformers/safe_division_transformer.py +95 -0
  194. pynecore/transformers/script_requirements.py +308 -0
  195. pynecore/transformers/security.py +752 -0
  196. pynecore/transformers/security_instantiation.py +274 -0
  197. pynecore/transformers/series.py +275 -0
  198. pynecore/transformers/slot_layout.py +381 -0
  199. pynecore/transformers/type_checking_stripper.py +25 -0
  200. pynecore/transformers/unused_series_detector.py +267 -0
  201. pynecore/types/__init__.py +21 -0
  202. pynecore/types/alert.py +5 -0
  203. pynecore/types/barmerge.py +5 -0
  204. pynecore/types/base.py +39 -0
  205. pynecore/types/box.py +37 -0
  206. pynecore/types/chart.py +17 -0
  207. pynecore/types/color.py +107 -0
  208. pynecore/types/currency.py +5 -0
  209. pynecore/types/datetime.py +6 -0
  210. pynecore/types/display.py +5 -0
  211. pynecore/types/dividends.py +5 -0
  212. pynecore/types/earnings.py +5 -0
  213. pynecore/types/extend.py +5 -0
  214. pynecore/types/font.py +5 -0
  215. pynecore/types/footprint.py +41 -0
  216. pynecore/types/format.py +5 -0
  217. pynecore/types/hline.py +24 -0
  218. pynecore/types/ib_persistent.py +8 -0
  219. pynecore/types/ib_persistent.pyi +10 -0
  220. pynecore/types/label.py +35 -0
  221. pynecore/types/line.py +32 -0
  222. pynecore/types/linefill.py +13 -0
  223. pynecore/types/location.py +5 -0
  224. pynecore/types/matrix.py +999 -0
  225. pynecore/types/na.py +237 -0
  226. pynecore/types/na.pyi +83 -0
  227. pynecore/types/ohlcv.py +12 -0
  228. pynecore/types/order.py +5 -0
  229. pynecore/types/persistent.py +8 -0
  230. pynecore/types/persistent.pyi +13 -0
  231. pynecore/types/pine_types.py +11 -0
  232. pynecore/types/pine_types.pyi +15 -0
  233. pynecore/types/pivotpointtype.py +5 -0
  234. pynecore/types/plot.py +12 -0
  235. pynecore/types/plot_meta.py +60 -0
  236. pynecore/types/polyline.py +40 -0
  237. pynecore/types/position.py +5 -0
  238. pynecore/types/scale.py +5 -0
  239. pynecore/types/script_type.py +15 -0
  240. pynecore/types/series.py +23 -0
  241. pynecore/types/series.pyi +19 -0
  242. pynecore/types/session.py +35 -0
  243. pynecore/types/shape.py +5 -0
  244. pynecore/types/size.py +5 -0
  245. pynecore/types/source.py +33 -0
  246. pynecore/types/splits.py +5 -0
  247. pynecore/types/strategy.py +45 -0
  248. pynecore/types/table.py +87 -0
  249. pynecore/types/text.py +13 -0
  250. pynecore/types/type_checker.py +7 -0
  251. pynecore/types/type_checker.pyi +48 -0
  252. pynecore/types/volume_row.py +36 -0
  253. pynecore/types/weekdays.py +11 -0
  254. pynecore/types/xloc.py +5 -0
  255. pynecore/types/yloc.py +5 -0
  256. pynecore/utils/__init__.py +0 -0
  257. pynecore/utils/file_utils.py +50 -0
  258. pynecore/utils/rich/__init__.py +0 -0
  259. pynecore/utils/rich/date_column.py +25 -0
  260. pynecore/utils/sequence_view.py +92 -0
  261. pynecore/utils/stdlib_checker.py +17 -0
@@ -0,0 +1,494 @@
1
+ """
2
+ Runtime core of the slot-based instance state scheme.
3
+
4
+ Function-instance state lives in plain lists ("state vectors") whose slots are
5
+ assigned at transform time; the emitted code addresses them with literal int
6
+ indexes. A child instance (the state of an isolated call site) occupies a
7
+ dedicated slot of its PARENT's state vector, so all live state forms a tree
8
+ hanging off a small set of root vectors (script main, library mains, security
9
+ processes). There is no global keyed instance cache: dropping a parent
10
+ releases its whole subtree through normal GC.
11
+
12
+ This module is the successor of the deleted ``function_isolation.py``
13
+ runtime (module-globals copying with a global keyed instance cache).
14
+
15
+ Layout metadata
16
+ ---------------
17
+
18
+ The transformer emits one ``__pyne_slot_layout__`` dict per module (one entry
19
+ per scope) and attaches the matching entry to every state-carrying function
20
+ as ``func.__pyne_layout__``. An entry is a plain dict with these keys:
21
+
22
+ ``init``
23
+ Tuple with the template value of every slot; ``list(init)`` is the
24
+ instantiation. The values are immutable by construction (literals or
25
+ ``NA``; non-literal initializers go through the lazy init-flag pattern),
26
+ so sharing them between instances without copying is safe. Series and
27
+ loop-site child slots hold ``None`` placeholders here.
28
+ ``series``
29
+ ``(slot, max_bars_back, elem)`` triples; :func:`_make_state` puts a fresh
30
+ :class:`~pynecore.core.series.SeriesImpl` into these slots. ``elem`` is
31
+ the statically known element type name (``'float'`` selects the native
32
+ nan as the series' out-of-range na value) or ``None``.
33
+ ``varip``
34
+ Slot indexes of ``varip`` variables (excluded from var rollback).
35
+ ``children``
36
+ ``(slot, call_id, in_loop)`` triples describing the isolated call sites
37
+ of the scope. Straight-line sites start as ``None`` and are filled by
38
+ :func:`__resolve_slot__` on first call; loop sites hold a list of child
39
+ states indexed by the per-invocation call counter and grown by
40
+ :func:`__grow__`.
41
+ ``names``
42
+ Optional tuple of per-slot debug names (same order as ``init``); used
43
+ only by :func:`explain_state` and the dump display-rewrite.
44
+
45
+ Call shapes emitted by the transformer:
46
+
47
+ - fast path, straight-line site::
48
+
49
+ ema((__st__ if (__st__ := __state__[5]) is not None
50
+ else __resolve_slot__(__state__, 5, ema)), close, 12)
51
+
52
+ - fast path, loop site (with the per-invocation counter ``__cnt_0__``)::
53
+
54
+ ema((__sl0__[__i__] if (__i__ := (__cnt_0__ := __cnt_0__ + 1) - 1) < len(__sl0__)
55
+ else __grow__(__sl0__, ema)), x, 12)
56
+
57
+ - uniform path (callee unknown at transform time), anchored at slot 7::
58
+
59
+ (__b__[1] if (__b__ := __state__[7]) is not None and __b__[0] is f
60
+ else __bind_any__(__state__, 7, f))(x)
61
+
62
+ - uniform path in a loop (anchor slot holds a list of ``(callee, bound)``
63
+ pairs indexed by the per-invocation counter, so every iteration keeps its
64
+ own instance, like the legacy counter-keyed cache did)::
65
+
66
+ (__b__[1] if (__i__ := (__cnt_0__ := __cnt_0__ + 1) - 1) < len(__chl_0__)
67
+ and (__b__ := __chl_0__[__i__])[0] is f
68
+ else __bind_any_loop__(__chl_0__, __i__, f))(x)
69
+
70
+ Semantics note: when the callee at a uniform site genuinely changes (``g = a
71
+ if c else b; g(x)``), the identity check misses and the site is rebound with
72
+ FRESH state. State does not survive an a -> b -> a swap; the legacy scheme did
73
+ not support that either (a cache hit there reused the first callee's instance
74
+ regardless of the current value). A miss caused merely by a per-bar
75
+ redefinition of the SAME logical callee (a method/function nested in ``main``
76
+ is a new object every bar) is NOT a change: the rebind reuses the prior state
77
+ vector (matched by the module-level layout object), so the callee's series /
78
+ var / varip slots survive across bars — see :func:`_carry_state`.
79
+ """
80
+ from typing import Any, Callable, Iterable
81
+ from copy import copy, deepcopy
82
+ from dataclasses import replace as dataclass_replace
83
+ from functools import partial
84
+
85
+ from .pine_export import Exported
86
+ from .series import SeriesImpl
87
+ from ..types.na import na_float as _NAN
88
+
89
+ __all__ = [
90
+ '__resolve_slot__', '__grow__', '__bind_any__', '__bind_any_loop__',
91
+ '__attach_layout__', '__dyn_default__',
92
+ 'create_root', 'get_root', 'discard_root', 'reset', 'register_shared_cache',
93
+ 'RootVarSnapshot', 'RootSeriesSnapshot', 'explain_state',
94
+ ]
95
+
96
+ # Sentinel for dynamic parameter defaults (DynamicDefaultTransformer). A
97
+ # default referencing per-bar runtime state (``lib.hl2`` etc.) must be
98
+ # evaluated per CALL, not at ``def`` time: an anchored call site binds the
99
+ # callee closure ONCE (an ``Exported`` proxy keeps a stable identity across
100
+ # per-bar redefinitions), so a def-time default would freeze the first bar's
101
+ # value. The transformer replaces such defaults with this sentinel and
102
+ # evaluates the original expression in the function body when the argument
103
+ # was omitted.
104
+ __dyn_default__ = object()
105
+
106
+ # Root state vectors by key; only roots are registered globally, every other
107
+ # instance lives in the tree hanging off them.
108
+ _root_vectors: dict[str, tuple[list, dict[str, Any]]] = {}
109
+
110
+ # Module-lifetime bound caches of the anchorless fallbacks (an overload
111
+ # dispatcher's own cache, method_call's per-method cache). They live outside
112
+ # the root-vector tree, so reset() clears them explicitly.
113
+ _shared_caches: list[dict] = []
114
+
115
+
116
+ def register_shared_cache(cache: dict) -> dict:
117
+ """Register a module-lifetime bound cache for clearing on :func:`reset`.
118
+
119
+ Anchorless call paths (direct dispatcher calls, ``method_call`` dispatch)
120
+ keep their bound instances in module-lifetime dicts instead of anchor
121
+ slots. The legacy runtime kept such state in its global instance cache,
122
+ which ``reset()`` dropped between runs — registering the dict keeps that
123
+ contract.
124
+
125
+ :param cache: The cache dict (held by reference, never replaced).
126
+ :return: The same dict, for inline registration at the definition site.
127
+ """
128
+ _shared_caches.append(cache)
129
+ return cache
130
+
131
+
132
+ def _make_state(layout: dict[str, Any]) -> list:
133
+ """Instantiate a state vector from a layout entry.
134
+
135
+ Template values are immutable by construction, so a flat ``list(init)``
136
+ needs no copying; the mutable content (series buffers, loop-site child
137
+ lists) is created fresh here.
138
+
139
+ :param layout: Layout entry (see module docstring).
140
+ :return: New state vector.
141
+ """
142
+ state = list(layout['init'])
143
+ for slot, max_bars_back, elem in layout['series']:
144
+ state[slot] = SeriesImpl(max_bars_back, _NAN if elem == 'float' else None)
145
+ for slot, _call_id, in_loop in layout['children']:
146
+ if in_loop:
147
+ state[slot] = []
148
+ return state
149
+
150
+
151
+ def __resolve_slot__(parent: list, slot: int, func: Any) -> list:
152
+ """Cold path of a straight-line fast-path call site: create the child
153
+ state and park it in the parent's slot.
154
+
155
+ :param parent: The caller's state vector.
156
+ :param slot: Child slot index assigned at transform time.
157
+ :param func: The state-carrying callee (carries ``__pyne_layout__``).
158
+ :return: The new child state vector.
159
+ """
160
+ state = _make_state(func.__pyne_layout__)
161
+ parent[slot] = state
162
+ return state
163
+
164
+
165
+ def __grow__(children: list, func: Any) -> list:
166
+ """Cold path of a loop-shaped fast-path call site: append a fresh child
167
+ state for a new loop iteration.
168
+
169
+ :param children: The child list living in the parent's slot.
170
+ :param func: The state-carrying callee (carries ``__pyne_layout__``).
171
+ :return: The new child state vector.
172
+ """
173
+ state = _make_state(func.__pyne_layout__)
174
+ children.append(state)
175
+ return state
176
+
177
+
178
+ def __attach_layout__(layout: dict[str, Any]) -> Callable[[Callable], Callable]:
179
+ """Decorator form of the layout attach, emitted for DECORATED
180
+ state-carrying definitions. It sits in the innermost decorator position,
181
+ so it tags the raw function before any other decorator (``overload`` in
182
+ particular) wraps or replaces it — the post-definition
183
+ ``func.__pyne_layout__ = ...`` assignment would tag the decorator's
184
+ return value instead.
185
+
186
+ :param layout: The function's layout entry.
187
+ :return: Identity decorator that attaches the layout.
188
+ """
189
+ def attach(func: Any) -> Callable:
190
+ func.__pyne_layout__ = layout
191
+ return func
192
+ return attach
193
+
194
+
195
+ def _carry_state(prev: tuple | None, layout: dict[str, Any]) -> list:
196
+ """State vector for a state-carrying callee at an anchored site: reuse the
197
+ prior anchor's vector when it belongs to the SAME logical callee, else make
198
+ a fresh one.
199
+
200
+ An identity miss at a uniform site has two causes that must not be
201
+ conflated. A genuinely different callee (``g = a if c else b; g(x)``) must
202
+ get fresh state. But a method/function nested in a per-bar ``main`` is a
203
+ BRAND-NEW function object every bar while remaining the same logical
204
+ callee, so its anchor also misses every bar — and there its series / var /
205
+ varip slots must SURVIVE, not reset. The discriminator is the module-level
206
+ layout object: it is the same dict for the same scope across bars and a
207
+ distinct dict for every other scope, so ``prev``'s layout being the new
208
+ callee's layout means "same callee, redefined" -> keep the state vector,
209
+ take the closure from the new object. This is the split
210
+ :func:`pine_method._bound_method` and ``overload._anchored`` already use;
211
+ a real ``a -> b -> a`` swap still loses state (distinct layouts), matching
212
+ the documented uniform-site semantics.
213
+
214
+ :param prev: The ``(callee, bound)`` pair previously parked in the anchor
215
+ slot, or ``None`` on the first bind.
216
+ :param layout: The new callee's layout entry.
217
+ :return: The state vector to bind.
218
+ """
219
+ if prev is not None:
220
+ prev_bound = prev[1]
221
+ if type(prev_bound) is partial and prev_bound.args \
222
+ and getattr(prev_bound.func, '__pyne_layout__', None) is layout:
223
+ return prev_bound.args[0]
224
+ return _make_state(layout)
225
+
226
+
227
+ def _bind_target(func: Any, prev: tuple | None = None) -> Callable:
228
+ """Binding logic of the uniform path: the legacy per-call entry guards
229
+ (type, classmethod, Exported unwrap) run here, once per binding, not per
230
+ call; state-carrying callees get a state vector baked into a partial,
231
+ reused from ``prev`` across a per-bar redefinition (see :func:`_carry_state`).
232
+
233
+ Callees that publish a ``__pyne_bind__`` factory (overload dispatchers)
234
+ get a fresh per-anchor binding from it — that is how the dispatcher
235
+ receives the caller's anchor and keeps one instance per implementation
236
+ in it.
237
+
238
+ :param func: The callee as it appears at the call site.
239
+ :param prev: The anchor's previous ``(callee, bound)`` entry, if any.
240
+ :return: The bound callable to invoke.
241
+ """
242
+ target = func
243
+ if isinstance(target, Exported):
244
+ target = target.__fn__
245
+ if target is None:
246
+ raise ValueError("Exported proxy has not been initialized with a function yet")
247
+ bind = getattr(target, '__pyne_bind__', None)
248
+ if bind is not None:
249
+ return bind()
250
+ if isinstance(target, type) or (
251
+ hasattr(target, '__self__') and isinstance(target.__self__, type)):
252
+ return target
253
+ layout = getattr(target, '__pyne_layout__', None)
254
+ return partial(target, _carry_state(prev, layout)) if layout is not None else target
255
+
256
+
257
+ def __bind_any__(parent: list, slot: int, func: Any) -> Callable:
258
+ """Bind a callee of unknown layout at an anchored call site (uniform
259
+ path).
260
+
261
+ The anchor key is the ORIGINAL call-site value (e.g. the ``Exported``
262
+ proxy itself), never the unwrapped function — the hot-path identity
263
+ check compares against the call-site value. A state-carrying callee
264
+ redefined for a new bar keeps its prior state vector (see
265
+ :func:`_carry_state`).
266
+
267
+ :param parent: The caller's state vector.
268
+ :param slot: Anchor slot index assigned at transform time.
269
+ :param func: The callee as it appears at the call site.
270
+ :return: The bound callable to invoke.
271
+ """
272
+ bound = _bind_target(func, parent[slot])
273
+ parent[slot] = (func, bound)
274
+ return bound
275
+
276
+
277
+ def __bind_any_loop__(children: list, index: int, func: Any) -> Callable:
278
+ """Bind a callee at a loop-shaped anchored call site: the anchor slot
279
+ holds a list of ``(callee, bound)`` pairs indexed by the per-invocation
280
+ counter, so each iteration keeps its own instance. Rebinds in place on
281
+ an identity miss, reusing the iteration's prior state vector when the same
282
+ logical callee was redefined for a new bar (see :func:`_carry_state`).
283
+
284
+ :param children: The pair list living in the parent's anchor slot.
285
+ :param index: Current iteration index (counter is sequential, so the
286
+ grow case is always ``index == len(children)``).
287
+ :param func: The callee as it appears at the call site.
288
+ :return: The bound callable to invoke.
289
+ """
290
+ prev = children[index] if index < len(children) else None
291
+ bound = _bind_target(func, prev)
292
+ entry = (func, bound)
293
+ if index < len(children):
294
+ children[index] = entry
295
+ else:
296
+ children.append(entry)
297
+ return bound
298
+
299
+
300
+ def create_root(key: str, layout: dict[str, Any]) -> list:
301
+ """Create (or recreate) a root state vector.
302
+
303
+ Roots belong to the entry points the runner drives directly: the script
304
+ ``main()``, library mains and security-process entries. Recreating an
305
+ existing key replaces the old root (a rerun drops the previous tree).
306
+
307
+ :param key: Unique root key (e.g. the module path of the entry point).
308
+ :param layout: Layout entry of the root scope.
309
+ :return: The new root state vector.
310
+ """
311
+ state = _make_state(layout)
312
+ _root_vectors[key] = (state, layout)
313
+ return state
314
+
315
+
316
+ def get_root(key: str) -> list | None:
317
+ """Return a registered root state vector, or ``None``.
318
+
319
+ :param key: Root key used at :func:`create_root`.
320
+ :return: The root state vector if registered.
321
+ """
322
+ entry = _root_vectors.get(key)
323
+ return entry[0] if entry is not None else None
324
+
325
+
326
+ def discard_root(key: str) -> None:
327
+ """Drop a root vector (its tree dies through GC). Missing keys are ignored.
328
+
329
+ :param key: Root key used at :func:`create_root`.
330
+ """
331
+ _root_vectors.pop(key, None)
332
+
333
+
334
+ def reset() -> None:
335
+ """Drop every function instance: clear the child slots of all root
336
+ vectors and the registered module-lifetime bound caches. Var and series
337
+ slots of the roots are left untouched — exact parity with the legacy
338
+ ``function_isolation.reset()``, which cleared the instance cache but
339
+ never touched main's own state.
340
+ """
341
+ for state, layout in _root_vectors.values():
342
+ for slot, _call_id, in_loop in layout['children']:
343
+ state[slot] = [] if in_loop else None
344
+ for cache in _shared_caches:
345
+ cache.clear()
346
+
347
+
348
+ def _var_slots(layout: dict[str, Any]) -> tuple[int, ...]:
349
+ """Slots subject to var rollback: everything that is not a series, varip
350
+ or child slot.
351
+
352
+ :param layout: Layout entry.
353
+ :return: Rollback slot indexes.
354
+ """
355
+ excluded = {slot for slot, _max_bars_back, _elem in layout['series']}
356
+ excluded.update(layout['varip'])
357
+ excluded.update(slot for slot, _call_id, _in_loop in layout['children'])
358
+ return tuple(i for i in range(len(layout['init'])) if i not in excluded)
359
+
360
+
361
+ def _copy_value(value: Any) -> Any:
362
+ """Copy a value for snapshot/restore: immutables as-is, dicts/lists by
363
+ deepcopy, dataclasses by ``replace``, everything else by shallow copy.
364
+
365
+ :param value: Value to copy.
366
+ :return: Copied (or immutable, as-is) value.
367
+ """
368
+ if isinstance(value, (int, float, bool, str, type(None))):
369
+ return value
370
+ if isinstance(value, (dict, list)):
371
+ return deepcopy(value)
372
+ try:
373
+ return dataclass_replace(value) # type: ignore[type-var]
374
+ except TypeError:
375
+ return copy(value)
376
+
377
+
378
+ class RootVarSnapshot:
379
+ """Snapshot/restore of the ``var`` slots of the root vectors, for the
380
+ calc_on_order_fills rollback. Parity with the legacy ``VarSnapshot``:
381
+ varip slots are excluded and isolated child instances are not touched.
382
+
383
+ Passing ``keys`` scopes the snapshot to specific roots — the runner uses
384
+ its own root keys, so interleaved runner instances never roll back each
385
+ other's state (the legacy snapshot was scoped to explicit modules).
386
+ """
387
+
388
+ __slots__ = ('_targets', '_snapshots')
389
+
390
+ def __init__(self, keys: Iterable[str] | None = None):
391
+ self._targets: list[tuple[list, tuple[int, ...]]] = []
392
+ self._snapshots: list[list] = []
393
+ entries = (_root_vectors.values() if keys is None
394
+ else (_root_vectors[key] for key in keys if key in _root_vectors))
395
+ for state, layout in entries:
396
+ slots = _var_slots(layout)
397
+ if slots:
398
+ self._targets.append((state, slots))
399
+
400
+ @property
401
+ def has_vars(self) -> bool:
402
+ """Whether any root has var slots to roll back."""
403
+ return bool(self._targets)
404
+
405
+ def save(self) -> None:
406
+ """Snapshot the var slots of all roots (called at bar start)."""
407
+ self._snapshots = [[_copy_value(state[i]) for i in slots]
408
+ for state, slots in self._targets]
409
+
410
+ def restore(self) -> None:
411
+ """Restore the var slots of all roots to the saved snapshot."""
412
+ for (state, slots), snapshot in zip(self._targets, self._snapshots):
413
+ for i, value in zip(slots, snapshot):
414
+ state[i] = _copy_value(value)
415
+
416
+
417
+ # noinspection PyProtectedMember
418
+ class RootSeriesSnapshot:
419
+ """Snapshot/restore of the ``series`` slots of the root vectors.
420
+
421
+ Companion to :class:`RootVarSnapshot` for the live
422
+ ``request.security_lower_tf`` LTF baseline. A reordered feed can force the
423
+ collector to replay an *earlier* ``bar_index`` after a later one already ran;
424
+ since :meth:`SeriesImpl.add` only overwrites for the current ``bar_index``,
425
+ that backward re-run would append and grow the buffer. ``RootVarSnapshot``
426
+ deliberately excludes series slots, so they need their own rollback.
427
+
428
+ Only the ROOT series slots are captured: a builtin price series like
429
+ ``close`` (the backing of ``close[1]``) is anchored in ``main`` by
430
+ ``LibrarySeriesTransformer``, so it lives in a root series slot. Child
431
+ (function-instance) series are dropped by :func:`reset` before every replay
432
+ and re-created fresh, so they never carry a backward-append across a replay
433
+ and need no snapshot here.
434
+ """
435
+
436
+ __slots__ = ('_targets', '_snapshots')
437
+
438
+ def __init__(self, keys: Iterable[str] | None = None):
439
+ self._targets: list[tuple[list, tuple[int, ...]]] = []
440
+ self._snapshots: list[list] = []
441
+ entries = (_root_vectors.values() if keys is None
442
+ else (_root_vectors[key] for key in keys if key in _root_vectors))
443
+ for state, layout in entries:
444
+ slots = tuple(slot for slot, _max_bars_back, _elem in layout['series'])
445
+ if slots:
446
+ self._targets.append((state, slots))
447
+
448
+ @property
449
+ def has_series(self) -> bool:
450
+ """Whether any root has series slots to roll back."""
451
+ return bool(self._targets)
452
+
453
+ @property
454
+ def saved(self) -> bool:
455
+ """Whether a snapshot has been captured (``save`` called since init)."""
456
+ return bool(self._snapshots)
457
+
458
+ def save(self) -> None:
459
+ """Snapshot the buffer state of every root series slot."""
460
+ self._snapshots = [[state[i]._snapshot() for i in slots]
461
+ for state, slots in self._targets]
462
+
463
+ def restore(self) -> None:
464
+ """Restore every root series slot to the saved snapshot (in place)."""
465
+ for (state, slots), snapshot in zip(self._targets, self._snapshots):
466
+ for i, snap in zip(slots, snapshot):
467
+ state[i]._restore(snap)
468
+
469
+
470
+ def explain_state(func_or_layout: Any, state: list) -> dict[str, Any]:
471
+ """Render a state vector as a readable name -> value dict (debug helper;
472
+ callable from a debugger watch window).
473
+
474
+ :param func_or_layout: A state-carrying function (``__pyne_layout__`` is
475
+ read off it) or a layout entry itself.
476
+ :param state: The instance's state vector.
477
+ :return: Slot name (or descriptive fallback label) -> current value.
478
+ """
479
+ layout: dict[str, Any] = getattr(func_or_layout, '__pyne_layout__', func_or_layout)
480
+ names = layout.get('names')
481
+ series_slots = {slot for slot, _max_bars_back, _elem in layout['series']}
482
+ child_ids = {slot: call_id for slot, call_id, _in_loop in layout['children']}
483
+ out: dict[str, Any] = {}
484
+ for i, value in enumerate(state):
485
+ if names and i < len(names) and names[i]:
486
+ label = names[i]
487
+ elif i in child_ids:
488
+ label = f'slot_{i}·child·{child_ids[i]}'
489
+ elif i in series_slots:
490
+ label = f'slot_{i}·series'
491
+ else:
492
+ label = f'slot_{i}'
493
+ out[label] = value
494
+ return out