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.
- opencode_pyneruntime-6.6.4.dist-info/METADATA +281 -0
- opencode_pyneruntime-6.6.4.dist-info/RECORD +261 -0
- opencode_pyneruntime-6.6.4.dist-info/WHEEL +5 -0
- opencode_pyneruntime-6.6.4.dist-info/entry_points.txt +6 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/LICENSE +201 -0
- opencode_pyneruntime-6.6.4.dist-info/licenses/NOTICE +21 -0
- opencode_pyneruntime-6.6.4.dist-info/top_level.txt +1 -0
- pynecore/__init__.py +6 -0
- pynecore/cli/__init__.py +2 -0
- pynecore/cli/app.py +238 -0
- pynecore/cli/commands/__init__.py +343 -0
- pynecore/cli/commands/benchmark.py +186 -0
- pynecore/cli/commands/compile.py +198 -0
- pynecore/cli/commands/data.py +857 -0
- pynecore/cli/commands/debug.py +63 -0
- pynecore/cli/commands/optimize.py +956 -0
- pynecore/cli/commands/plugin.py +242 -0
- pynecore/cli/commands/run.py +2006 -0
- pynecore/cli/pluggable.py +132 -0
- pynecore/cli/utils/__init__.py +0 -0
- pynecore/cli/utils/api_error_handler.py +168 -0
- pynecore/cli/utils/broker_picker.py +330 -0
- pynecore/cli/utils/error_hook.py +28 -0
- pynecore/cli/utils/keyreader.py +178 -0
- pynecore/cli/utils/provider_picker.py +19 -0
- pynecore/cli/utils/symbol_browser.py +1149 -0
- pynecore/core/__init__.py +0 -0
- pynecore/core/aggregator.py +257 -0
- pynecore/core/bar_magnifier.py +168 -0
- pynecore/core/broker/__init__.py +64 -0
- pynecore/core/broker/defaults.py +113 -0
- pynecore/core/broker/disappearance.py +927 -0
- pynecore/core/broker/emulator.py +345 -0
- pynecore/core/broker/exceptions.py +346 -0
- pynecore/core/broker/idempotency.py +401 -0
- pynecore/core/broker/intent_builder.py +334 -0
- pynecore/core/broker/journal.py +1785 -0
- pynecore/core/broker/models.py +1600 -0
- pynecore/core/broker/native_failsafe_manager.py +1436 -0
- pynecore/core/broker/one_way_emulator.py +1128 -0
- pynecore/core/broker/position.py +787 -0
- pynecore/core/broker/run_identity.py +126 -0
- pynecore/core/broker/software_entry_stop_engine.py +351 -0
- pynecore/core/broker/software_partial_bracket_engine.py +1379 -0
- pynecore/core/broker/spot_inventory.py +1327 -0
- pynecore/core/broker/storage.py +2655 -0
- pynecore/core/broker/store_helpers.py +2161 -0
- pynecore/core/broker/sync_engine.py +16070 -0
- pynecore/core/broker/validation.py +382 -0
- pynecore/core/class_property.py +7 -0
- pynecore/core/config.py +392 -0
- pynecore/core/csv_file.py +547 -0
- pynecore/core/currency.py +262 -0
- pynecore/core/data_converter.py +1002 -0
- pynecore/core/datetime.py +296 -0
- pynecore/core/download_info.py +71 -0
- pynecore/core/download_runner.py +274 -0
- pynecore/core/htf_aggregator.py +181 -0
- pynecore/core/import_hook.py +358 -0
- pynecore/core/instance_state.py +494 -0
- pynecore/core/live_ltf_collector.py +442 -0
- pynecore/core/live_ltf_window.py +189 -0
- pynecore/core/live_runner.py +1347 -0
- pynecore/core/module_property.py +26 -0
- pynecore/core/ohlcv_file.py +1888 -0
- pynecore/core/overload.py +371 -0
- pynecore/core/pine_cast.py +113 -0
- pynecore/core/pine_export.py +95 -0
- pynecore/core/pine_method.py +244 -0
- pynecore/core/pine_range.py +86 -0
- pynecore/core/pine_udt.py +69 -0
- pynecore/core/plugin/__init__.py +394 -0
- pynecore/core/plugin/broker.py +781 -0
- pynecore/core/plugin/cli.py +96 -0
- pynecore/core/plugin/live_provider.py +208 -0
- pynecore/core/plugin/provider.py +331 -0
- pynecore/core/provider_string.py +148 -0
- pynecore/core/random.py +40 -0
- pynecore/core/resampler.py +686 -0
- pynecore/core/safe_convert.py +64 -0
- pynecore/core/script.py +1011 -0
- pynecore/core/script_runner.py +3202 -0
- pynecore/core/security.py +1749 -0
- pynecore/core/security_process.py +1253 -0
- pynecore/core/security_shm.py +456 -0
- pynecore/core/series.py +417 -0
- pynecore/core/strategy_stats.py +669 -0
- pynecore/core/symbol_map.py +134 -0
- pynecore/core/syminfo.py +505 -0
- pynecore/core/viz.py +591 -0
- pynecore/lib/__init__.py +1771 -0
- pynecore/lib/_fixnan.py +32 -0
- pynecore/lib/_math_stateful.py +202 -0
- pynecore/lib/_timeframe_change.py +101 -0
- pynecore/lib/adjustment.py +6 -0
- pynecore/lib/alert.py +39 -0
- pynecore/lib/alert.pyi +14 -0
- pynecore/lib/array.py +1051 -0
- pynecore/lib/barmerge.py +60 -0
- pynecore/lib/barstate.py +30 -0
- pynecore/lib/box.py +415 -0
- pynecore/lib/chart.py +128 -0
- pynecore/lib/color.py +152 -0
- pynecore/lib/color.pyi +50 -0
- pynecore/lib/currency.py +62 -0
- pynecore/lib/dayofweek.py +36 -0
- pynecore/lib/dayofweek.pyi +18 -0
- pynecore/lib/display.py +8 -0
- pynecore/lib/dividends.py +9 -0
- pynecore/lib/earnings.py +11 -0
- pynecore/lib/extend.py +6 -0
- pynecore/lib/font.py +5 -0
- pynecore/lib/footprint.py +79 -0
- pynecore/lib/format.py +11 -0
- pynecore/lib/hline.py +67 -0
- pynecore/lib/hline.pyi +24 -0
- pynecore/lib/label.py +409 -0
- pynecore/lib/line.py +433 -0
- pynecore/lib/linefill.py +93 -0
- pynecore/lib/location.py +11 -0
- pynecore/lib/log.py +362 -0
- pynecore/lib/map.py +150 -0
- pynecore/lib/math.py +385 -0
- pynecore/lib/matrix.py +708 -0
- pynecore/lib/order.py +8 -0
- pynecore/lib/pivotpointtype.py +8 -0
- pynecore/lib/plot.py +95 -0
- pynecore/lib/plot.pyi +33 -0
- pynecore/lib/polyline.py +91 -0
- pynecore/lib/position.py +15 -0
- pynecore/lib/request.py +281 -0
- pynecore/lib/runtime.py +5 -0
- pynecore/lib/scale.py +9 -0
- pynecore/lib/session.py +267 -0
- pynecore/lib/session.pyi +12 -0
- pynecore/lib/shape.py +18 -0
- pynecore/lib/size.py +12 -0
- pynecore/lib/splits.py +4 -0
- pynecore/lib/strategy/__init__.py +4778 -0
- pynecore/lib/strategy/closedtrades.py +347 -0
- pynecore/lib/strategy/closedtrades.pyi +53 -0
- pynecore/lib/strategy/commission.py +9 -0
- pynecore/lib/strategy/direction.py +9 -0
- pynecore/lib/strategy/oca.py +13 -0
- pynecore/lib/strategy/opentrades.py +281 -0
- pynecore/lib/strategy/opentrades.pyi +49 -0
- pynecore/lib/strategy/risk.py +109 -0
- pynecore/lib/string.py +649 -0
- pynecore/lib/syminfo.py +84 -0
- pynecore/lib/ta.py +2230 -0
- pynecore/lib/table.py +290 -0
- pynecore/lib/text.py +17 -0
- pynecore/lib/ticker.py +207 -0
- pynecore/lib/timeframe.py +293 -0
- pynecore/lib/volume_row.py +67 -0
- pynecore/lib/xloc.py +4 -0
- pynecore/lib/yloc.py +5 -0
- pynecore/providers/__init__.py +0 -0
- pynecore/providers/ccxt.py +664 -0
- pynecore/providers/replay.py +187 -0
- pynecore/pynesys/__init__.py +0 -0
- pynecore/pynesys/api.py +498 -0
- pynecore/pynesys/compiler.py +112 -0
- pynecore/standalone.py +99 -0
- pynecore/testing/__init__.py +1 -0
- pynecore/testing/broker_lab/__init__.py +41 -0
- pynecore/testing/broker_lab/__main__.py +5 -0
- pynecore/testing/broker_lab/cli.py +87 -0
- pynecore/testing/broker_lab/generate.py +47 -0
- pynecore/testing/broker_lab/model.py +84 -0
- pynecore/testing/broker_lab/reference.py +645 -0
- pynecore/testing/broker_lab/runner.py +372 -0
- pynecore/testing/broker_lab/scheduler.py +50 -0
- pynecore/testing/broker_lab/subprocess.py +73 -0
- pynecore/transformers/__init__.py +0 -0
- pynecore/transformers/builtin_shadow.py +136 -0
- pynecore/transformers/closure_arguments_transformer.py +428 -0
- pynecore/transformers/display_rewrite.py +140 -0
- pynecore/transformers/dynamic_default.py +147 -0
- pynecore/transformers/function_isolation.py +757 -0
- pynecore/transformers/import_lifter.py +61 -0
- pynecore/transformers/import_normalizer.py +328 -0
- pynecore/transformers/inline_series_hoist.py +178 -0
- pynecore/transformers/input_transformer.py +175 -0
- pynecore/transformers/lib_series.py +201 -0
- pynecore/transformers/locations.py +70 -0
- pynecore/transformers/module_properties.json +3387 -0
- pynecore/transformers/module_property.py +221 -0
- pynecore/transformers/ne_guard.py +70 -0
- pynecore/transformers/persistent.py +320 -0
- pynecore/transformers/persistent_series.py +76 -0
- pynecore/transformers/safe_convert_transformer.py +97 -0
- pynecore/transformers/safe_division_transformer.py +95 -0
- pynecore/transformers/script_requirements.py +308 -0
- pynecore/transformers/security.py +752 -0
- pynecore/transformers/security_instantiation.py +274 -0
- pynecore/transformers/series.py +275 -0
- pynecore/transformers/slot_layout.py +381 -0
- pynecore/transformers/type_checking_stripper.py +25 -0
- pynecore/transformers/unused_series_detector.py +267 -0
- pynecore/types/__init__.py +21 -0
- pynecore/types/alert.py +5 -0
- pynecore/types/barmerge.py +5 -0
- pynecore/types/base.py +39 -0
- pynecore/types/box.py +37 -0
- pynecore/types/chart.py +17 -0
- pynecore/types/color.py +107 -0
- pynecore/types/currency.py +5 -0
- pynecore/types/datetime.py +6 -0
- pynecore/types/display.py +5 -0
- pynecore/types/dividends.py +5 -0
- pynecore/types/earnings.py +5 -0
- pynecore/types/extend.py +5 -0
- pynecore/types/font.py +5 -0
- pynecore/types/footprint.py +41 -0
- pynecore/types/format.py +5 -0
- pynecore/types/hline.py +24 -0
- pynecore/types/ib_persistent.py +8 -0
- pynecore/types/ib_persistent.pyi +10 -0
- pynecore/types/label.py +35 -0
- pynecore/types/line.py +32 -0
- pynecore/types/linefill.py +13 -0
- pynecore/types/location.py +5 -0
- pynecore/types/matrix.py +999 -0
- pynecore/types/na.py +237 -0
- pynecore/types/na.pyi +83 -0
- pynecore/types/ohlcv.py +12 -0
- pynecore/types/order.py +5 -0
- pynecore/types/persistent.py +8 -0
- pynecore/types/persistent.pyi +13 -0
- pynecore/types/pine_types.py +11 -0
- pynecore/types/pine_types.pyi +15 -0
- pynecore/types/pivotpointtype.py +5 -0
- pynecore/types/plot.py +12 -0
- pynecore/types/plot_meta.py +60 -0
- pynecore/types/polyline.py +40 -0
- pynecore/types/position.py +5 -0
- pynecore/types/scale.py +5 -0
- pynecore/types/script_type.py +15 -0
- pynecore/types/series.py +23 -0
- pynecore/types/series.pyi +19 -0
- pynecore/types/session.py +35 -0
- pynecore/types/shape.py +5 -0
- pynecore/types/size.py +5 -0
- pynecore/types/source.py +33 -0
- pynecore/types/splits.py +5 -0
- pynecore/types/strategy.py +45 -0
- pynecore/types/table.py +87 -0
- pynecore/types/text.py +13 -0
- pynecore/types/type_checker.py +7 -0
- pynecore/types/type_checker.pyi +48 -0
- pynecore/types/volume_row.py +36 -0
- pynecore/types/weekdays.py +11 -0
- pynecore/types/xloc.py +5 -0
- pynecore/types/yloc.py +5 -0
- pynecore/utils/__init__.py +0 -0
- pynecore/utils/file_utils.py +50 -0
- pynecore/utils/rich/__init__.py +0 -0
- pynecore/utils/rich/date_column.py +25 -0
- pynecore/utils/sequence_view.py +92 -0
- 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
|